Merge tag 'atmel-hlcdc-drm-3.20' of https://github.com/bbrezillon/linux-at91 into drm-next
Add atmel HLCDC driver. * tag 'atmel-hlcdc-drm-3.20' of https://github.com/bbrezillon/linux-at91: drm: add DT bindings documentation for atmel-hlcdc-dc driver drm: add Atmel HLCDC Display Controller support drm: panel: simple-panel: add bus format information for foxlink panel drm: panel: simple-panel: add support for bus_format retrieval drm: add bus_formats and num_bus_formats fields to drm_display_info
This commit is contained in:
commit
c0d2792b2a
53
Documentation/devicetree/bindings/drm/atmel/hlcdc-dc.txt
Normal file
53
Documentation/devicetree/bindings/drm/atmel/hlcdc-dc.txt
Normal file
@ -0,0 +1,53 @@
|
||||
Device-Tree bindings for Atmel's HLCDC (High LCD Controller) DRM driver
|
||||
|
||||
The Atmel HLCDC Display Controller is subdevice of the HLCDC MFD device.
|
||||
See ../mfd/atmel-hlcdc.txt for more details.
|
||||
|
||||
Required properties:
|
||||
- compatible: value should be "atmel,hlcdc-display-controller"
|
||||
- pinctrl-names: the pin control state names. Should contain "default".
|
||||
- pinctrl-0: should contain the default pinctrl states.
|
||||
- #address-cells: should be set to 1.
|
||||
- #size-cells: should be set to 0.
|
||||
|
||||
Required children nodes:
|
||||
Children nodes are encoding available output ports and their connections
|
||||
to external devices using the OF graph reprensentation (see ../graph.txt).
|
||||
At least one port node is required.
|
||||
|
||||
Example:
|
||||
|
||||
hlcdc: hlcdc@f0030000 {
|
||||
compatible = "atmel,sama5d3-hlcdc";
|
||||
reg = <0xf0030000 0x2000>;
|
||||
interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
|
||||
clocks = <&lcdc_clk>, <&lcdck>, <&clk32k>;
|
||||
clock-names = "periph_clk","sys_clk", "slow_clk";
|
||||
status = "disabled";
|
||||
|
||||
hlcdc-display-controller {
|
||||
compatible = "atmel,hlcdc-display-controller";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_lcd_base &pinctrl_lcd_rgb888>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
reg = <0>;
|
||||
|
||||
hlcdc_panel_output: endpoint@0 {
|
||||
reg = <0>;
|
||||
remote-endpoint = <&panel_input>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
hlcdc_pwm: hlcdc-pwm {
|
||||
compatible = "atmel,hlcdc-pwm";
|
||||
pinctrl-names = "default";
|
||||
pinctrl-0 = <&pinctrl_lcd_pwm>;
|
||||
#pwm-cells = <3>;
|
||||
};
|
||||
};
|
@ -183,6 +183,8 @@ source "drivers/gpu/drm/cirrus/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/armada/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/atmel-hlcdc/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/rcar-du/Kconfig"
|
||||
|
||||
source "drivers/gpu/drm/shmobile/Kconfig"
|
||||
|
@ -54,6 +54,7 @@ obj-$(CONFIG_DRM_GMA500) += gma500/
|
||||
obj-$(CONFIG_DRM_UDL) += udl/
|
||||
obj-$(CONFIG_DRM_AST) += ast/
|
||||
obj-$(CONFIG_DRM_ARMADA) += armada/
|
||||
obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc/
|
||||
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du/
|
||||
obj-$(CONFIG_DRM_SHMOBILE) +=shmobile/
|
||||
obj-$(CONFIG_DRM_OMAP) += omapdrm/
|
||||
|
11
drivers/gpu/drm/atmel-hlcdc/Kconfig
Normal file
11
drivers/gpu/drm/atmel-hlcdc/Kconfig
Normal file
@ -0,0 +1,11 @@
|
||||
config DRM_ATMEL_HLCDC
|
||||
tristate "DRM Support for ATMEL HLCDC Display Controller"
|
||||
depends on DRM && OF && COMMON_CLK && MFD_ATMEL_HLCDC
|
||||
select DRM_GEM_CMA_HELPER
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_KMS_FB_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).
|
7
drivers/gpu/drm/atmel-hlcdc/Makefile
Normal file
7
drivers/gpu/drm/atmel-hlcdc/Makefile
Normal file
@ -0,0 +1,7 @@
|
||||
atmel-hlcdc-dc-y := atmel_hlcdc_crtc.o \
|
||||
atmel_hlcdc_dc.o \
|
||||
atmel_hlcdc_layer.o \
|
||||
atmel_hlcdc_output.o \
|
||||
atmel_hlcdc_plane.o
|
||||
|
||||
obj-$(CONFIG_DRM_ATMEL_HLCDC) += atmel-hlcdc-dc.o
|
406
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
Normal file
406
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_crtc.c
Normal file
@ -0,0 +1,406 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Traphandler
|
||||
* Copyright (C) 2014 Free Electrons
|
||||
*
|
||||
* Author: Jean-Jacques Hiblot <jjhiblot@traphandler.com>
|
||||
* Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include <video/videomode.h>
|
||||
|
||||
#include "atmel_hlcdc_dc.h"
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* @dpms: DPMS mode
|
||||
*/
|
||||
struct atmel_hlcdc_crtc {
|
||||
struct drm_crtc base;
|
||||
struct atmel_hlcdc_dc *dc;
|
||||
struct drm_pending_vblank_event *event;
|
||||
int id;
|
||||
int dpms;
|
||||
};
|
||||
|
||||
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_dpms(struct drm_crtc *c, int mode)
|
||||
{
|
||||
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;
|
||||
|
||||
if (mode != DRM_MODE_DPMS_ON)
|
||||
mode = DRM_MODE_DPMS_OFF;
|
||||
|
||||
if (crtc->dpms == mode)
|
||||
return;
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
|
||||
if (mode != DRM_MODE_DPMS_ON) {
|
||||
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);
|
||||
|
||||
pm_runtime_allow(dev->dev);
|
||||
} else {
|
||||
pm_runtime_forbid(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);
|
||||
|
||||
crtc->dpms = mode;
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_crtc_mode_set(struct drm_crtc *c,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adj,
|
||||
int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
|
||||
struct regmap *regmap = crtc->dc->hlcdc->regmap;
|
||||
struct drm_plane *plane = c->primary;
|
||||
struct drm_framebuffer *fb;
|
||||
unsigned long mode_rate;
|
||||
struct videomode vm;
|
||||
unsigned long prate;
|
||||
unsigned int cfg;
|
||||
int div;
|
||||
|
||||
if (atmel_hlcdc_dc_mode_valid(crtc->dc, adj) != MODE_OK)
|
||||
return -EINVAL;
|
||||
|
||||
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));
|
||||
|
||||
cfg = ATMEL_HLCDC_CLKPOL;
|
||||
|
||||
prate = clk_get_rate(crtc->dc->hlcdc->sys_clk);
|
||||
mode_rate = mode->crtc_clock * 1000;
|
||||
if ((prate / 2) < mode_rate) {
|
||||
prate *= 2;
|
||||
cfg |= ATMEL_HLCDC_CLKSEL;
|
||||
}
|
||||
|
||||
div = DIV_ROUND_UP(prate, mode_rate);
|
||||
if (div < 2)
|
||||
div = 2;
|
||||
|
||||
cfg |= ATMEL_HLCDC_CLKDIV(div);
|
||||
|
||||
regmap_update_bits(regmap, ATMEL_HLCDC_CFG(0),
|
||||
ATMEL_HLCDC_CLKSEL | ATMEL_HLCDC_CLKDIV_MASK |
|
||||
ATMEL_HLCDC_CLKPOL, cfg);
|
||||
|
||||
cfg = 0;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
cfg |= ATMEL_HLCDC_VSPOL;
|
||||
|
||||
if (mode->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,
|
||||
cfg);
|
||||
|
||||
fb = plane->fb;
|
||||
plane->fb = old_fb;
|
||||
|
||||
return atmel_hlcdc_plane_update_with_mode(plane, c, fb, 0, 0,
|
||||
adj->hdisplay, adj->vdisplay,
|
||||
x << 16, y << 16,
|
||||
adj->hdisplay << 16,
|
||||
adj->vdisplay << 16,
|
||||
adj);
|
||||
}
|
||||
|
||||
int atmel_hlcdc_crtc_mode_set_base(struct drm_crtc *c, int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct drm_plane *plane = c->primary;
|
||||
struct drm_framebuffer *fb = plane->fb;
|
||||
struct drm_display_mode *mode = &c->hwmode;
|
||||
|
||||
plane->fb = old_fb;
|
||||
|
||||
return plane->funcs->update_plane(plane, c, fb,
|
||||
0, 0,
|
||||
mode->hdisplay,
|
||||
mode->vdisplay,
|
||||
x << 16, y << 16,
|
||||
mode->hdisplay << 16,
|
||||
mode->vdisplay << 16);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_crtc_prepare(struct drm_crtc *crtc)
|
||||
{
|
||||
atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_crtc_commit(struct drm_crtc *crtc)
|
||||
{
|
||||
atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static bool atmel_hlcdc_crtc_mode_fixup(struct drm_crtc *crtc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_crtc_disable(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_plane *plane;
|
||||
|
||||
atmel_hlcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
crtc->primary->funcs->disable_plane(crtc->primary);
|
||||
|
||||
drm_for_each_legacy_plane(plane, &crtc->dev->mode_config.plane_list) {
|
||||
if (plane->crtc != crtc)
|
||||
continue;
|
||||
|
||||
plane->funcs->disable_plane(crtc->primary);
|
||||
plane->crtc = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct drm_crtc_helper_funcs lcdc_crtc_helper_funcs = {
|
||||
.mode_fixup = atmel_hlcdc_crtc_mode_fixup,
|
||||
.dpms = atmel_hlcdc_crtc_dpms,
|
||||
.mode_set = atmel_hlcdc_crtc_mode_set,
|
||||
.mode_set_base = atmel_hlcdc_crtc_mode_set_base,
|
||||
.prepare = atmel_hlcdc_crtc_prepare,
|
||||
.commit = atmel_hlcdc_crtc_commit,
|
||||
.disable = atmel_hlcdc_crtc_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);
|
||||
}
|
||||
|
||||
void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *c,
|
||||
struct drm_file *file)
|
||||
{
|
||||
struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
|
||||
struct drm_pending_vblank_event *event;
|
||||
struct drm_device *dev = c->dev;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
event = crtc->event;
|
||||
if (event && event->base.file_priv == file) {
|
||||
event->base.destroy(&event->base);
|
||||
drm_vblank_put(dev, crtc->id);
|
||||
crtc->event = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
}
|
||||
|
||||
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_send_vblank_event(dev, crtc->id, crtc->event);
|
||||
drm_vblank_put(dev, crtc->id);
|
||||
crtc->event = NULL;
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
}
|
||||
|
||||
void atmel_hlcdc_crtc_irq(struct drm_crtc *c)
|
||||
{
|
||||
drm_handle_vblank(c->dev, 0);
|
||||
atmel_hlcdc_crtc_finish_page_flip(drm_crtc_to_atmel_hlcdc_crtc(c));
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_crtc_page_flip(struct drm_crtc *c,
|
||||
struct drm_framebuffer *fb,
|
||||
struct drm_pending_vblank_event *event,
|
||||
uint32_t page_flip_flags)
|
||||
{
|
||||
struct atmel_hlcdc_crtc *crtc = drm_crtc_to_atmel_hlcdc_crtc(c);
|
||||
struct atmel_hlcdc_plane_update_req req;
|
||||
struct drm_plane *plane = c->primary;
|
||||
struct drm_device *dev = c->dev;
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
if (crtc->event)
|
||||
ret = -EBUSY;
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.crtc_x = 0;
|
||||
req.crtc_y = 0;
|
||||
req.crtc_h = c->mode.crtc_vdisplay;
|
||||
req.crtc_w = c->mode.crtc_hdisplay;
|
||||
req.src_x = c->x << 16;
|
||||
req.src_y = c->y << 16;
|
||||
req.src_w = req.crtc_w << 16;
|
||||
req.src_h = req.crtc_h << 16;
|
||||
req.fb = fb;
|
||||
|
||||
ret = atmel_hlcdc_plane_prepare_update_req(plane, &req, &c->hwmode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (event) {
|
||||
drm_vblank_get(c->dev, crtc->id);
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
crtc->event = event;
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
}
|
||||
|
||||
ret = atmel_hlcdc_plane_apply_update_req(plane, &req);
|
||||
if (ret)
|
||||
crtc->event = NULL;
|
||||
else
|
||||
plane->fb = fb;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct drm_crtc_funcs atmel_hlcdc_crtc_funcs = {
|
||||
.page_flip = atmel_hlcdc_crtc_page_flip,
|
||||
.set_config = drm_crtc_helper_set_config,
|
||||
.destroy = atmel_hlcdc_crtc_destroy,
|
||||
};
|
||||
|
||||
int atmel_hlcdc_crtc_create(struct drm_device *dev)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
struct atmel_hlcdc_planes *planes = dc->planes;
|
||||
struct atmel_hlcdc_crtc *crtc;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
crtc = kzalloc(sizeof(*crtc), GFP_KERNEL);
|
||||
if (!crtc)
|
||||
return -ENOMEM;
|
||||
|
||||
crtc->dpms = DRM_MODE_DPMS_OFF;
|
||||
crtc->dc = dc;
|
||||
|
||||
ret = drm_crtc_init_with_planes(dev, &crtc->base,
|
||||
&planes->primary->base,
|
||||
planes->cursor ? &planes->cursor->base : NULL,
|
||||
&atmel_hlcdc_crtc_funcs);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
crtc->id = drm_crtc_index(&crtc->base);
|
||||
|
||||
if (planes->cursor)
|
||||
planes->cursor->base.possible_crtcs = 1 << crtc->id;
|
||||
|
||||
for (i = 0; i < planes->noverlays; i++)
|
||||
planes->overlays[i]->base.possible_crtcs = 1 << crtc->id;
|
||||
|
||||
drm_crtc_helper_add(&crtc->base, &lcdc_crtc_helper_funcs);
|
||||
|
||||
dc->crtc = &crtc->base;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
atmel_hlcdc_crtc_destroy(&crtc->base);
|
||||
return ret;
|
||||
}
|
||||
|
579
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
Normal file
579
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.c
Normal file
@ -0,0 +1,579 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqchip.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
|
||||
#include "atmel_hlcdc_dc.h"
|
||||
|
||||
#define ATMEL_HLCDC_LAYER_IRQS_OFFSET 8
|
||||
|
||||
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,
|
||||
.nconfigs = 7,
|
||||
.layout = {
|
||||
.xstride = { 2 },
|
||||
.default_color = 3,
|
||||
.general_config = 4,
|
||||
.disc_pos = 5,
|
||||
.disc_size = 6,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "overlay1",
|
||||
.formats = &atmel_hlcdc_plane_rgb_formats,
|
||||
.regs_offset = 0x140,
|
||||
.id = 1,
|
||||
.type = ATMEL_HLCDC_OVERLAY_LAYER,
|
||||
.nconfigs = 10,
|
||||
.layout = {
|
||||
.pos = 2,
|
||||
.size = 3,
|
||||
.xstride = { 4 },
|
||||
.pstride = { 5 },
|
||||
.default_color = 6,
|
||||
.chroma_key = 7,
|
||||
.chroma_key_mask = 8,
|
||||
.general_config = 9,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "overlay2",
|
||||
.formats = &atmel_hlcdc_plane_rgb_formats,
|
||||
.regs_offset = 0x240,
|
||||
.id = 2,
|
||||
.type = ATMEL_HLCDC_OVERLAY_LAYER,
|
||||
.nconfigs = 10,
|
||||
.layout = {
|
||||
.pos = 2,
|
||||
.size = 3,
|
||||
.xstride = { 4 },
|
||||
.pstride = { 5 },
|
||||
.default_color = 6,
|
||||
.chroma_key = 7,
|
||||
.chroma_key_mask = 8,
|
||||
.general_config = 9,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "high-end-overlay",
|
||||
.formats = &atmel_hlcdc_plane_rgb_and_yuv_formats,
|
||||
.regs_offset = 0x340,
|
||||
.id = 3,
|
||||
.type = ATMEL_HLCDC_OVERLAY_LAYER,
|
||||
.nconfigs = 42,
|
||||
.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,
|
||||
.csc = 14,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "cursor",
|
||||
.formats = &atmel_hlcdc_plane_rgb_formats,
|
||||
.regs_offset = 0x440,
|
||||
.id = 4,
|
||||
.type = ATMEL_HLCDC_CURSOR_LAYER,
|
||||
.nconfigs = 10,
|
||||
.max_width = 128,
|
||||
.max_height = 128,
|
||||
.layout = {
|
||||
.pos = 2,
|
||||
.size = 3,
|
||||
.xstride = { 4 },
|
||||
.pstride = { 5 },
|
||||
.default_color = 6,
|
||||
.chroma_key = 7,
|
||||
.chroma_key_mask = 8,
|
||||
.general_config = 9,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
static const struct atmel_hlcdc_dc_desc atmel_hlcdc_dc_sama5d3 = {
|
||||
.min_width = 0,
|
||||
.min_height = 0,
|
||||
.max_width = 2048,
|
||||
.max_height = 2048,
|
||||
.nlayers = ARRAY_SIZE(atmel_hlcdc_sama5d3_layers),
|
||||
.layers = atmel_hlcdc_sama5d3_layers,
|
||||
};
|
||||
|
||||
static const struct of_device_id atmel_hlcdc_of_match[] = {
|
||||
{
|
||||
.compatible = "atmel,sama5d3-hlcdc",
|
||||
.data = &atmel_hlcdc_dc_sama5d3,
|
||||
},
|
||||
{ /* sentinel */ },
|
||||
};
|
||||
|
||||
int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc,
|
||||
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 > 0x40 || hsync_len < 1)
|
||||
return MODE_HSYNC;
|
||||
|
||||
if (vsync_len > 0x40 || vsync_len < 1)
|
||||
return MODE_VSYNC;
|
||||
|
||||
if (hfront_porch > 0x200 || hfront_porch < 1 ||
|
||||
hback_porch > 0x200 || hback_porch < 1 ||
|
||||
mode->hdisplay < 1)
|
||||
return MODE_H_ILLEGAL;
|
||||
|
||||
if (vfront_porch > 0x40 || vfront_porch < 1 ||
|
||||
vback_porch > 0x40 || vback_porch < 0 ||
|
||||
mode->vdisplay < 1)
|
||||
return MODE_V_ILLEGAL;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
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++) {
|
||||
struct atmel_hlcdc_layer *layer = dc->layers[i];
|
||||
|
||||
if (!(ATMEL_HLCDC_LAYER_STATUS(i) & status) || !layer)
|
||||
continue;
|
||||
|
||||
atmel_hlcdc_layer_irq(layer);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static struct drm_framebuffer *atmel_hlcdc_fb_create(struct drm_device *dev,
|
||||
struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd)
|
||||
{
|
||||
return drm_fb_cma_create(dev, file_priv, mode_cmd);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_fb_output_poll_changed(struct drm_device *dev)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
|
||||
if (dc->fbdev) {
|
||||
drm_fbdev_cma_hotplug_event(dc->fbdev);
|
||||
} else {
|
||||
dc->fbdev = drm_fbdev_cma_init(dev, 24,
|
||||
dev->mode_config.num_crtc,
|
||||
dev->mode_config.num_connector);
|
||||
if (IS_ERR(dc->fbdev))
|
||||
dc->fbdev = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct drm_mode_config_funcs mode_config_funcs = {
|
||||
.fb_create = atmel_hlcdc_fb_create,
|
||||
.output_poll_changed = atmel_hlcdc_fb_output_poll_changed,
|
||||
};
|
||||
|
||||
static int atmel_hlcdc_dc_modeset_init(struct drm_device *dev)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
struct atmel_hlcdc_planes *planes;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
drm_mode_config_init(dev);
|
||||
|
||||
ret = atmel_hlcdc_create_outputs(dev);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to create panel: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
planes = atmel_hlcdc_create_planes(dev);
|
||||
if (IS_ERR(planes)) {
|
||||
dev_err(dev->dev, "failed to create planes\n");
|
||||
return PTR_ERR(planes);
|
||||
}
|
||||
|
||||
dc->planes = planes;
|
||||
|
||||
dc->layers[planes->primary->layer.desc->id] =
|
||||
&planes->primary->layer;
|
||||
|
||||
if (planes->cursor)
|
||||
dc->layers[planes->cursor->layer.desc->id] =
|
||||
&planes->cursor->layer;
|
||||
|
||||
for (i = 0; i < planes->noverlays; i++)
|
||||
dc->layers[planes->overlays[i]->layer.desc->id] =
|
||||
&planes->overlays[i]->layer;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ret = drm_vblank_init(dev, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to initialize vblank\n");
|
||||
goto err_periph_clk_disable;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
/* force connectors detection */
|
||||
drm_helper_hpd_irq_event(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;
|
||||
|
||||
if (dc->fbdev)
|
||||
drm_fbdev_cma_fini(dc->fbdev);
|
||||
flush_workqueue(dc->wq);
|
||||
drm_kms_helper_poll_fini(dev);
|
||||
drm_mode_config_cleanup(dev);
|
||||
drm_vblank_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_connector_plug_all(struct drm_device *dev)
|
||||
{
|
||||
struct drm_connector *connector, *failed;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&dev->mode_config.mutex);
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
ret = drm_connector_register(connector);
|
||||
if (ret) {
|
||||
failed = connector;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
return 0;
|
||||
|
||||
err:
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
if (failed == connector)
|
||||
break;
|
||||
|
||||
drm_connector_unregister(connector);
|
||||
}
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_dc_connector_unplug_all(struct drm_device *dev)
|
||||
{
|
||||
mutex_lock(&dev->mode_config.mutex);
|
||||
drm_connector_unplug_all(dev);
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_dc_preclose(struct drm_device *dev,
|
||||
struct drm_file *file)
|
||||
{
|
||||
struct drm_crtc *crtc;
|
||||
|
||||
list_for_each_entry(crtc, &dev->mode_config.crtc_list, head)
|
||||
atmel_hlcdc_crtc_cancel_page_flip(crtc, file);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_dc_lastclose(struct drm_device *dev)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
|
||||
drm_fbdev_cma_restore_mode(dc->fbdev);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_dc_enable_vblank(struct drm_device *dev, int crtc)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
|
||||
/* Enable SOF (Start Of Frame) interrupt for vblank counting */
|
||||
regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IER, ATMEL_HLCDC_SOF);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_dc_disable_vblank(struct drm_device *dev, int crtc)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
|
||||
regmap_write(dc->hlcdc->regmap, ATMEL_HLCDC_IDR, ATMEL_HLCDC_SOF);
|
||||
}
|
||||
|
||||
static const struct file_operations fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = drm_open,
|
||||
.release = drm_release,
|
||||
.unlocked_ioctl = drm_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = drm_compat_ioctl,
|
||||
#endif
|
||||
.poll = drm_poll,
|
||||
.read = drm_read,
|
||||
.llseek = no_llseek,
|
||||
.mmap = drm_gem_cma_mmap,
|
||||
};
|
||||
|
||||
static struct drm_driver atmel_hlcdc_dc_driver = {
|
||||
.driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
|
||||
.preclose = atmel_hlcdc_dc_preclose,
|
||||
.lastclose = atmel_hlcdc_dc_lastclose,
|
||||
.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,
|
||||
.get_vblank_counter = drm_vblank_count,
|
||||
.enable_vblank = atmel_hlcdc_dc_enable_vblank,
|
||||
.disable_vblank = atmel_hlcdc_dc_disable_vblank,
|
||||
.gem_free_object = drm_gem_cma_free_object,
|
||||
.gem_vm_ops = &drm_gem_cma_vm_ops,
|
||||
.dumb_create = drm_gem_cma_dumb_create,
|
||||
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
|
||||
.dumb_destroy = drm_gem_dumb_destroy,
|
||||
.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 (!ddev)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = drm_dev_set_unique(ddev, dev_name(ddev->dev));
|
||||
if (ret)
|
||||
goto err_unref;
|
||||
|
||||
ret = atmel_hlcdc_dc_load(ddev);
|
||||
if (ret)
|
||||
goto err_unref;
|
||||
|
||||
ret = drm_dev_register(ddev, 0);
|
||||
if (ret)
|
||||
goto err_unload;
|
||||
|
||||
ret = atmel_hlcdc_dc_connector_plug_all(ddev);
|
||||
if (ret)
|
||||
goto err_unregister;
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
drm_dev_unregister(ddev);
|
||||
|
||||
err_unload:
|
||||
atmel_hlcdc_dc_unload(ddev);
|
||||
|
||||
err_unref:
|
||||
drm_dev_unref(ddev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_dc_drm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct drm_device *ddev = platform_get_drvdata(pdev);
|
||||
|
||||
atmel_hlcdc_dc_connector_unplug_all(ddev);
|
||||
drm_dev_unregister(ddev);
|
||||
atmel_hlcdc_dc_unload(ddev);
|
||||
drm_dev_unref(ddev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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",
|
||||
.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");
|
213
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
Normal file
213
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_dc.h
Normal file
@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DRM_ATMEL_HLCDC_H
|
||||
#define DRM_ATMEL_HLCDC_H
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/pwm.h>
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_fb_cma_helper.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include "atmel_hlcdc_layer.h"
|
||||
|
||||
#define ATMEL_HLCDC_MAX_LAYERS 5
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Display Controller description structure.
|
||||
*
|
||||
* This structure describe 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
|
||||
* @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;
|
||||
const struct atmel_hlcdc_layer_desc *layers;
|
||||
int nlayers;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Plane properties.
|
||||
*
|
||||
* This structure stores plane property definitions.
|
||||
*
|
||||
* @alpha: alpha blending (or transparency) property
|
||||
* @rotation: rotation property
|
||||
*/
|
||||
struct atmel_hlcdc_plane_properties {
|
||||
struct drm_property *alpha;
|
||||
struct drm_property *rotation;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Plane.
|
||||
*
|
||||
* @base: base DRM plane structure
|
||||
* @layer: HLCDC layer structure
|
||||
* @properties: pointer to the property definitions structure
|
||||
* @rotation: current rotation status
|
||||
*/
|
||||
struct atmel_hlcdc_plane {
|
||||
struct drm_plane base;
|
||||
struct atmel_hlcdc_layer layer;
|
||||
struct atmel_hlcdc_plane_properties *properties;
|
||||
unsigned int rotation;
|
||||
};
|
||||
|
||||
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 *l)
|
||||
{
|
||||
return container_of(l, struct atmel_hlcdc_plane, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Plane update request structure.
|
||||
*
|
||||
* @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
|
||||
* @fb: framebuffer object object
|
||||
* @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)
|
||||
*/
|
||||
struct atmel_hlcdc_plane_update_req {
|
||||
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;
|
||||
struct drm_framebuffer *fb;
|
||||
|
||||
/* These fields are private and should not be touched */
|
||||
int bpp[ATMEL_HLCDC_MAX_PLANES];
|
||||
unsigned int offsets[ATMEL_HLCDC_MAX_PLANES];
|
||||
int xstride[ATMEL_HLCDC_MAX_PLANES];
|
||||
int pstride[ATMEL_HLCDC_MAX_PLANES];
|
||||
int nplanes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Planes.
|
||||
*
|
||||
* This structure stores the instantiated HLCDC Planes and can be accessed by
|
||||
* the HLCDC Display Controller or the HLCDC CRTC.
|
||||
*
|
||||
* @primary: primary plane
|
||||
* @cursor: hardware cursor plane
|
||||
* @overlays: overlay plane table
|
||||
* @noverlays: number of overlay planes
|
||||
*/
|
||||
struct atmel_hlcdc_planes {
|
||||
struct atmel_hlcdc_plane *primary;
|
||||
struct atmel_hlcdc_plane *cursor;
|
||||
struct atmel_hlcdc_plane **overlays;
|
||||
int noverlays;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Display Controller.
|
||||
*
|
||||
* @desc: HLCDC Display Controller description
|
||||
* @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 layer
|
||||
* @wq: display controller workqueue
|
||||
*/
|
||||
struct atmel_hlcdc_dc {
|
||||
const struct atmel_hlcdc_dc_desc *desc;
|
||||
struct atmel_hlcdc *hlcdc;
|
||||
struct drm_fbdev_cma *fbdev;
|
||||
struct drm_crtc *crtc;
|
||||
struct atmel_hlcdc_planes *planes;
|
||||
struct atmel_hlcdc_layer *layers[ATMEL_HLCDC_MAX_LAYERS];
|
||||
struct workqueue_struct *wq;
|
||||
};
|
||||
|
||||
extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_formats;
|
||||
extern struct atmel_hlcdc_formats atmel_hlcdc_plane_rgb_and_yuv_formats;
|
||||
|
||||
int atmel_hlcdc_dc_mode_valid(struct atmel_hlcdc_dc *dc,
|
||||
struct drm_display_mode *mode);
|
||||
|
||||
struct atmel_hlcdc_planes *
|
||||
atmel_hlcdc_create_planes(struct drm_device *dev);
|
||||
|
||||
int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p,
|
||||
struct atmel_hlcdc_plane_update_req *req,
|
||||
const struct drm_display_mode *mode);
|
||||
|
||||
int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p,
|
||||
struct atmel_hlcdc_plane_update_req *req);
|
||||
|
||||
int atmel_hlcdc_plane_update_with_mode(struct drm_plane *p,
|
||||
struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *fb,
|
||||
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,
|
||||
const struct drm_display_mode *mode);
|
||||
|
||||
void atmel_hlcdc_crtc_irq(struct drm_crtc *c);
|
||||
|
||||
void atmel_hlcdc_crtc_cancel_page_flip(struct drm_crtc *crtc,
|
||||
struct drm_file *file);
|
||||
|
||||
int atmel_hlcdc_crtc_create(struct drm_device *dev);
|
||||
|
||||
int atmel_hlcdc_create_outputs(struct drm_device *dev);
|
||||
|
||||
#endif /* DRM_ATMEL_HLCDC_H */
|
667
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
Normal file
667
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.c
Normal file
@ -0,0 +1,667 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Free Electrons
|
||||
* Copyright (C) 2014 Atmel
|
||||
*
|
||||
* Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
#include "atmel_hlcdc_dc.h"
|
||||
|
||||
static void
|
||||
atmel_hlcdc_layer_fb_flip_release(struct drm_flip_work *work, void *val)
|
||||
{
|
||||
struct atmel_hlcdc_layer_fb_flip *flip = val;
|
||||
|
||||
if (flip->fb)
|
||||
drm_framebuffer_unreference(flip->fb);
|
||||
kfree(flip);
|
||||
}
|
||||
|
||||
static void
|
||||
atmel_hlcdc_layer_fb_flip_destroy(struct atmel_hlcdc_layer_fb_flip *flip)
|
||||
{
|
||||
if (flip->fb)
|
||||
drm_framebuffer_unreference(flip->fb);
|
||||
kfree(flip->task);
|
||||
kfree(flip);
|
||||
}
|
||||
|
||||
static void
|
||||
atmel_hlcdc_layer_fb_flip_release_queue(struct atmel_hlcdc_layer *layer,
|
||||
struct atmel_hlcdc_layer_fb_flip *flip)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!flip)
|
||||
return;
|
||||
|
||||
for (i = 0; i < layer->max_planes; i++) {
|
||||
if (!flip->dscrs[i])
|
||||
break;
|
||||
|
||||
flip->dscrs[i]->status = 0;
|
||||
flip->dscrs[i] = NULL;
|
||||
}
|
||||
|
||||
drm_flip_work_queue_task(&layer->gc, flip->task);
|
||||
drm_flip_work_commit(&layer->gc, layer->wq);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_layer_update_reset(struct atmel_hlcdc_layer *layer,
|
||||
int id)
|
||||
{
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
struct atmel_hlcdc_layer_update_slot *slot;
|
||||
|
||||
if (id < 0 || id > 1)
|
||||
return;
|
||||
|
||||
slot = &upd->slots[id];
|
||||
bitmap_clear(slot->updated_configs, 0, layer->desc->nconfigs);
|
||||
memset(slot->configs, 0,
|
||||
sizeof(*slot->configs) * layer->desc->nconfigs);
|
||||
|
||||
if (slot->fb_flip) {
|
||||
atmel_hlcdc_layer_fb_flip_release_queue(layer, slot->fb_flip);
|
||||
slot->fb_flip = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_layer_update_apply(struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
|
||||
const struct atmel_hlcdc_layer_desc *desc = layer->desc;
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
struct regmap *regmap = layer->hlcdc->regmap;
|
||||
struct atmel_hlcdc_layer_update_slot *slot;
|
||||
struct atmel_hlcdc_layer_fb_flip *fb_flip;
|
||||
struct atmel_hlcdc_dma_channel_dscr *dscr;
|
||||
unsigned int cfg;
|
||||
u32 action = 0;
|
||||
int i = 0;
|
||||
|
||||
if (upd->pending < 0 || upd->pending > 1)
|
||||
return;
|
||||
|
||||
slot = &upd->slots[upd->pending];
|
||||
|
||||
for_each_set_bit(cfg, slot->updated_configs, layer->desc->nconfigs) {
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_CFG(layer, cfg),
|
||||
slot->configs[cfg]);
|
||||
action |= ATMEL_HLCDC_LAYER_UPDATE;
|
||||
}
|
||||
|
||||
fb_flip = slot->fb_flip;
|
||||
|
||||
if (!fb_flip->fb)
|
||||
goto apply;
|
||||
|
||||
if (dma->status == ATMEL_HLCDC_LAYER_DISABLED) {
|
||||
for (i = 0; i < fb_flip->ngems; i++) {
|
||||
dscr = fb_flip->dscrs[i];
|
||||
dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH |
|
||||
ATMEL_HLCDC_LAYER_DMA_IRQ |
|
||||
ATMEL_HLCDC_LAYER_ADD_IRQ |
|
||||
ATMEL_HLCDC_LAYER_DONE_IRQ;
|
||||
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_PLANE_ADDR(i),
|
||||
dscr->addr);
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_PLANE_CTRL(i),
|
||||
dscr->ctrl);
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_PLANE_NEXT(i),
|
||||
dscr->next);
|
||||
}
|
||||
|
||||
action |= ATMEL_HLCDC_LAYER_DMA_CHAN;
|
||||
dma->status = ATMEL_HLCDC_LAYER_ENABLED;
|
||||
} else {
|
||||
for (i = 0; i < fb_flip->ngems; i++) {
|
||||
dscr = fb_flip->dscrs[i];
|
||||
dscr->ctrl = ATMEL_HLCDC_LAYER_DFETCH |
|
||||
ATMEL_HLCDC_LAYER_DMA_IRQ |
|
||||
ATMEL_HLCDC_LAYER_DSCR_IRQ |
|
||||
ATMEL_HLCDC_LAYER_DONE_IRQ;
|
||||
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_PLANE_HEAD(i),
|
||||
dscr->next);
|
||||
}
|
||||
|
||||
action |= ATMEL_HLCDC_LAYER_A2Q;
|
||||
}
|
||||
|
||||
/* Release unneeded descriptors */
|
||||
for (i = fb_flip->ngems; i < layer->max_planes; i++) {
|
||||
fb_flip->dscrs[i]->status = 0;
|
||||
fb_flip->dscrs[i] = NULL;
|
||||
}
|
||||
|
||||
dma->queue = fb_flip;
|
||||
slot->fb_flip = NULL;
|
||||
|
||||
apply:
|
||||
if (action)
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset + ATMEL_HLCDC_LAYER_CHER,
|
||||
action);
|
||||
|
||||
atmel_hlcdc_layer_update_reset(layer, upd->pending);
|
||||
|
||||
upd->pending = -1;
|
||||
}
|
||||
|
||||
void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
|
||||
const struct atmel_hlcdc_layer_desc *desc = layer->desc;
|
||||
struct regmap *regmap = layer->hlcdc->regmap;
|
||||
struct atmel_hlcdc_layer_fb_flip *flip;
|
||||
unsigned long flags;
|
||||
unsigned int isr, imr;
|
||||
unsigned int status;
|
||||
unsigned int plane_status;
|
||||
u32 flip_status;
|
||||
|
||||
int i;
|
||||
|
||||
regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IMR, &imr);
|
||||
regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR, &isr);
|
||||
status = imr & isr;
|
||||
if (!status)
|
||||
return;
|
||||
|
||||
spin_lock_irqsave(&layer->lock, flags);
|
||||
|
||||
flip = dma->queue ? dma->queue : dma->cur;
|
||||
|
||||
if (!flip) {
|
||||
spin_unlock_irqrestore(&layer->lock, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set LOADED and DONE flags: they'll be cleared if at least one
|
||||
* memory plane is not LOADED or DONE.
|
||||
*/
|
||||
flip_status = ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED |
|
||||
ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE;
|
||||
for (i = 0; i < flip->ngems; i++) {
|
||||
plane_status = (status >> (8 * i));
|
||||
|
||||
if (plane_status &
|
||||
(ATMEL_HLCDC_LAYER_ADD_IRQ |
|
||||
ATMEL_HLCDC_LAYER_DSCR_IRQ) &
|
||||
~flip->dscrs[i]->ctrl) {
|
||||
flip->dscrs[i]->status |=
|
||||
ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED;
|
||||
flip->dscrs[i]->ctrl |=
|
||||
ATMEL_HLCDC_LAYER_ADD_IRQ |
|
||||
ATMEL_HLCDC_LAYER_DSCR_IRQ;
|
||||
}
|
||||
|
||||
if (plane_status &
|
||||
ATMEL_HLCDC_LAYER_DONE_IRQ &
|
||||
~flip->dscrs[i]->ctrl) {
|
||||
flip->dscrs[i]->status |=
|
||||
ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE;
|
||||
flip->dscrs[i]->ctrl |=
|
||||
ATMEL_HLCDC_LAYER_DONE_IRQ;
|
||||
}
|
||||
|
||||
if (plane_status & ATMEL_HLCDC_LAYER_OVR_IRQ)
|
||||
flip->dscrs[i]->status |=
|
||||
ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN;
|
||||
|
||||
/*
|
||||
* Clear LOADED and DONE flags if the memory plane is either
|
||||
* not LOADED or not DONE.
|
||||
*/
|
||||
if (!(flip->dscrs[i]->status &
|
||||
ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED))
|
||||
flip_status &= ~ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED;
|
||||
|
||||
if (!(flip->dscrs[i]->status &
|
||||
ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE))
|
||||
flip_status &= ~ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE;
|
||||
|
||||
/*
|
||||
* An overrun on one memory plane impact the whole framebuffer
|
||||
* transfer, hence we set the OVERRUN flag as soon as there's
|
||||
* one memory plane reporting such an overrun.
|
||||
*/
|
||||
flip_status |= flip->dscrs[i]->status &
|
||||
ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN;
|
||||
}
|
||||
|
||||
/* Get changed bits */
|
||||
flip_status ^= flip->status;
|
||||
flip->status |= flip_status;
|
||||
|
||||
if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_LOADED) {
|
||||
atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur);
|
||||
dma->cur = dma->queue;
|
||||
dma->queue = NULL;
|
||||
}
|
||||
|
||||
if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_DONE) {
|
||||
atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur);
|
||||
dma->cur = NULL;
|
||||
}
|
||||
|
||||
if (flip_status & ATMEL_HLCDC_DMA_CHANNEL_DSCR_OVERRUN) {
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
|
||||
ATMEL_HLCDC_LAYER_RST);
|
||||
if (dma->queue)
|
||||
atmel_hlcdc_layer_fb_flip_release_queue(layer,
|
||||
dma->queue);
|
||||
|
||||
if (dma->cur)
|
||||
atmel_hlcdc_layer_fb_flip_release_queue(layer,
|
||||
dma->cur);
|
||||
|
||||
dma->cur = NULL;
|
||||
dma->queue = NULL;
|
||||
}
|
||||
|
||||
if (!dma->queue) {
|
||||
atmel_hlcdc_layer_update_apply(layer);
|
||||
|
||||
if (!dma->cur)
|
||||
dma->status = ATMEL_HLCDC_LAYER_DISABLED;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&layer->lock, flags);
|
||||
}
|
||||
|
||||
int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
struct regmap *regmap = layer->hlcdc->regmap;
|
||||
const struct atmel_hlcdc_layer_desc *desc = layer->desc;
|
||||
unsigned long flags;
|
||||
unsigned int isr;
|
||||
|
||||
spin_lock_irqsave(&layer->lock, flags);
|
||||
|
||||
/* Disable the layer */
|
||||
regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
|
||||
ATMEL_HLCDC_LAYER_RST);
|
||||
|
||||
/* Clear all pending interrupts */
|
||||
regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR, &isr);
|
||||
|
||||
/* Discard current and queued framebuffer transfers. */
|
||||
if (dma->cur) {
|
||||
atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->cur);
|
||||
dma->cur = NULL;
|
||||
}
|
||||
|
||||
if (dma->queue) {
|
||||
atmel_hlcdc_layer_fb_flip_release_queue(layer, dma->queue);
|
||||
dma->queue = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Then discard the pending update request (if any) to prevent
|
||||
* DMA irq handler from restarting the DMA channel after it has
|
||||
* been disabled.
|
||||
*/
|
||||
if (upd->pending >= 0) {
|
||||
atmel_hlcdc_layer_update_reset(layer, upd->pending);
|
||||
upd->pending = -1;
|
||||
}
|
||||
|
||||
dma->status = ATMEL_HLCDC_LAYER_DISABLED;
|
||||
|
||||
spin_unlock_irqrestore(&layer->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int atmel_hlcdc_layer_update_start(struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
struct regmap *regmap = layer->hlcdc->regmap;
|
||||
struct atmel_hlcdc_layer_fb_flip *fb_flip;
|
||||
struct atmel_hlcdc_layer_update_slot *slot;
|
||||
unsigned long flags;
|
||||
int i, j = 0;
|
||||
|
||||
fb_flip = kzalloc(sizeof(*fb_flip), GFP_KERNEL);
|
||||
if (!fb_flip)
|
||||
return -ENOMEM;
|
||||
|
||||
fb_flip->task = drm_flip_work_allocate_task(fb_flip, GFP_KERNEL);
|
||||
if (!fb_flip->task) {
|
||||
kfree(fb_flip);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&layer->lock, flags);
|
||||
|
||||
upd->next = upd->pending ? 0 : 1;
|
||||
|
||||
slot = &upd->slots[upd->next];
|
||||
|
||||
for (i = 0; i < layer->max_planes * 4; i++) {
|
||||
if (!dma->dscrs[i].status) {
|
||||
fb_flip->dscrs[j++] = &dma->dscrs[i];
|
||||
dma->dscrs[i].status =
|
||||
ATMEL_HLCDC_DMA_CHANNEL_DSCR_RESERVED;
|
||||
if (j == layer->max_planes)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j < layer->max_planes) {
|
||||
for (i = 0; i < j; i++)
|
||||
fb_flip->dscrs[i]->status = 0;
|
||||
}
|
||||
|
||||
if (j < layer->max_planes) {
|
||||
spin_unlock_irqrestore(&layer->lock, flags);
|
||||
atmel_hlcdc_layer_fb_flip_destroy(fb_flip);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
slot->fb_flip = fb_flip;
|
||||
|
||||
if (upd->pending >= 0) {
|
||||
memcpy(slot->configs,
|
||||
upd->slots[upd->pending].configs,
|
||||
layer->desc->nconfigs * sizeof(u32));
|
||||
memcpy(slot->updated_configs,
|
||||
upd->slots[upd->pending].updated_configs,
|
||||
DIV_ROUND_UP(layer->desc->nconfigs,
|
||||
BITS_PER_BYTE * sizeof(unsigned long)) *
|
||||
sizeof(unsigned long));
|
||||
slot->fb_flip->fb = upd->slots[upd->pending].fb_flip->fb;
|
||||
if (upd->slots[upd->pending].fb_flip->fb) {
|
||||
slot->fb_flip->fb =
|
||||
upd->slots[upd->pending].fb_flip->fb;
|
||||
slot->fb_flip->ngems =
|
||||
upd->slots[upd->pending].fb_flip->ngems;
|
||||
drm_framebuffer_reference(slot->fb_flip->fb);
|
||||
}
|
||||
} else {
|
||||
regmap_bulk_read(regmap,
|
||||
layer->desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_CFG(layer, 0),
|
||||
upd->slots[upd->next].configs,
|
||||
layer->desc->nconfigs);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&layer->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
|
||||
atmel_hlcdc_layer_update_reset(layer, upd->next);
|
||||
upd->next = -1;
|
||||
}
|
||||
|
||||
void atmel_hlcdc_layer_update_set_fb(struct atmel_hlcdc_layer *layer,
|
||||
struct drm_framebuffer *fb,
|
||||
unsigned int *offsets)
|
||||
{
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
struct atmel_hlcdc_layer_fb_flip *fb_flip;
|
||||
struct atmel_hlcdc_layer_update_slot *slot;
|
||||
struct atmel_hlcdc_dma_channel_dscr *dscr;
|
||||
struct drm_framebuffer *old_fb;
|
||||
int nplanes = 0;
|
||||
int i;
|
||||
|
||||
if (upd->next < 0 || upd->next > 1)
|
||||
return;
|
||||
|
||||
if (fb)
|
||||
nplanes = drm_format_num_planes(fb->pixel_format);
|
||||
|
||||
if (nplanes > layer->max_planes)
|
||||
return;
|
||||
|
||||
slot = &upd->slots[upd->next];
|
||||
|
||||
fb_flip = slot->fb_flip;
|
||||
old_fb = slot->fb_flip->fb;
|
||||
|
||||
for (i = 0; i < nplanes; i++) {
|
||||
struct drm_gem_cma_object *gem;
|
||||
|
||||
dscr = slot->fb_flip->dscrs[i];
|
||||
gem = drm_fb_cma_get_gem_obj(fb, i);
|
||||
dscr->addr = gem->paddr + offsets[i];
|
||||
}
|
||||
|
||||
fb_flip->ngems = nplanes;
|
||||
fb_flip->fb = fb;
|
||||
|
||||
if (fb)
|
||||
drm_framebuffer_reference(fb);
|
||||
|
||||
if (old_fb)
|
||||
drm_framebuffer_unreference(old_fb);
|
||||
}
|
||||
|
||||
void atmel_hlcdc_layer_update_cfg(struct atmel_hlcdc_layer *layer, int cfg,
|
||||
u32 mask, u32 val)
|
||||
{
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
struct atmel_hlcdc_layer_update_slot *slot;
|
||||
|
||||
if (upd->next < 0 || upd->next > 1)
|
||||
return;
|
||||
|
||||
if (cfg >= layer->desc->nconfigs)
|
||||
return;
|
||||
|
||||
slot = &upd->slots[upd->next];
|
||||
slot->configs[cfg] &= ~mask;
|
||||
slot->configs[cfg] |= (val & mask);
|
||||
set_bit(cfg, slot->updated_configs);
|
||||
}
|
||||
|
||||
void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
struct atmel_hlcdc_layer_update_slot *slot;
|
||||
unsigned long flags;
|
||||
|
||||
if (upd->next < 0 || upd->next > 1)
|
||||
return;
|
||||
|
||||
slot = &upd->slots[upd->next];
|
||||
|
||||
spin_lock_irqsave(&layer->lock, flags);
|
||||
|
||||
/*
|
||||
* Release pending update request and replace it by the new one.
|
||||
*/
|
||||
if (upd->pending >= 0)
|
||||
atmel_hlcdc_layer_update_reset(layer, upd->pending);
|
||||
|
||||
upd->pending = upd->next;
|
||||
upd->next = -1;
|
||||
|
||||
if (!dma->queue)
|
||||
atmel_hlcdc_layer_update_apply(layer);
|
||||
|
||||
spin_unlock_irqrestore(&layer->lock, flags);
|
||||
|
||||
|
||||
upd->next = -1;
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_layer_dma_init(struct drm_device *dev,
|
||||
struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
|
||||
dma_addr_t dma_addr;
|
||||
int i;
|
||||
|
||||
dma->dscrs = dma_alloc_coherent(dev->dev,
|
||||
layer->max_planes * 4 *
|
||||
sizeof(*dma->dscrs),
|
||||
&dma_addr, GFP_KERNEL);
|
||||
if (!dma->dscrs)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < layer->max_planes * 4; i++) {
|
||||
struct atmel_hlcdc_dma_channel_dscr *dscr = &dma->dscrs[i];
|
||||
|
||||
dscr->next = dma_addr + (i * sizeof(*dscr));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_layer_dma_cleanup(struct drm_device *dev,
|
||||
struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
struct atmel_hlcdc_layer_dma_channel *dma = &layer->dma;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < layer->max_planes * 4; i++) {
|
||||
struct atmel_hlcdc_dma_channel_dscr *dscr = &dma->dscrs[i];
|
||||
|
||||
dscr->status = 0;
|
||||
}
|
||||
|
||||
dma_free_coherent(dev->dev, layer->max_planes * 4 *
|
||||
sizeof(*dma->dscrs), dma->dscrs,
|
||||
dma->dscrs[0].next);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_layer_update_init(struct drm_device *dev,
|
||||
struct atmel_hlcdc_layer *layer,
|
||||
const struct atmel_hlcdc_layer_desc *desc)
|
||||
{
|
||||
struct atmel_hlcdc_layer_update *upd = &layer->update;
|
||||
int updated_size;
|
||||
void *buffer;
|
||||
int i;
|
||||
|
||||
updated_size = DIV_ROUND_UP(desc->nconfigs,
|
||||
BITS_PER_BYTE *
|
||||
sizeof(unsigned long));
|
||||
|
||||
buffer = devm_kzalloc(dev->dev,
|
||||
((desc->nconfigs * sizeof(u32)) +
|
||||
(updated_size * sizeof(unsigned long))) * 2,
|
||||
GFP_KERNEL);
|
||||
if (!buffer)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
upd->slots[i].updated_configs = buffer;
|
||||
buffer += updated_size * sizeof(unsigned long);
|
||||
upd->slots[i].configs = buffer;
|
||||
buffer += desc->nconfigs * sizeof(u32);
|
||||
}
|
||||
|
||||
upd->pending = -1;
|
||||
upd->next = -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int atmel_hlcdc_layer_init(struct drm_device *dev,
|
||||
struct atmel_hlcdc_layer *layer,
|
||||
const struct atmel_hlcdc_layer_desc *desc)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
struct regmap *regmap = dc->hlcdc->regmap;
|
||||
unsigned int tmp;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
layer->hlcdc = dc->hlcdc;
|
||||
layer->wq = dc->wq;
|
||||
layer->desc = desc;
|
||||
|
||||
regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
|
||||
ATMEL_HLCDC_LAYER_RST);
|
||||
for (i = 0; i < desc->formats->nformats; i++) {
|
||||
int nplanes = drm_format_num_planes(desc->formats->formats[i]);
|
||||
|
||||
if (nplanes > layer->max_planes)
|
||||
layer->max_planes = nplanes;
|
||||
}
|
||||
|
||||
spin_lock_init(&layer->lock);
|
||||
drm_flip_work_init(&layer->gc, desc->name,
|
||||
atmel_hlcdc_layer_fb_flip_release);
|
||||
ret = atmel_hlcdc_layer_dma_init(dev, layer);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = atmel_hlcdc_layer_update_init(dev, layer, desc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Flush Status Register */
|
||||
regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IDR,
|
||||
0xffffffff);
|
||||
regmap_read(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_ISR,
|
||||
&tmp);
|
||||
|
||||
tmp = 0;
|
||||
for (i = 0; i < layer->max_planes; i++)
|
||||
tmp |= (ATMEL_HLCDC_LAYER_DMA_IRQ |
|
||||
ATMEL_HLCDC_LAYER_DSCR_IRQ |
|
||||
ATMEL_HLCDC_LAYER_ADD_IRQ |
|
||||
ATMEL_HLCDC_LAYER_DONE_IRQ |
|
||||
ATMEL_HLCDC_LAYER_OVR_IRQ) << (8 * i);
|
||||
|
||||
regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IER, tmp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void atmel_hlcdc_layer_cleanup(struct drm_device *dev,
|
||||
struct atmel_hlcdc_layer *layer)
|
||||
{
|
||||
const struct atmel_hlcdc_layer_desc *desc = layer->desc;
|
||||
struct regmap *regmap = layer->hlcdc->regmap;
|
||||
|
||||
regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_IDR,
|
||||
0xffffffff);
|
||||
regmap_write(regmap, desc->regs_offset + ATMEL_HLCDC_LAYER_CHDR,
|
||||
ATMEL_HLCDC_LAYER_RST);
|
||||
|
||||
atmel_hlcdc_layer_dma_cleanup(dev, layer);
|
||||
drm_flip_work_cleanup(&layer->gc);
|
||||
}
|
398
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
Normal file
398
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_layer.h
Normal file
@ -0,0 +1,398 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Free Electrons
|
||||
* Copyright (C) 2014 Atmel
|
||||
*
|
||||
* Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef DRM_ATMEL_HLCDC_LAYER_H
|
||||
#define DRM_ATMEL_HLCDC_LAYER_H
|
||||
|
||||
#include <linux/mfd/atmel-hlcdc.h>
|
||||
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_flip_work.h>
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#define ATMEL_HLCDC_LAYER_CHER 0x0
|
||||
#define ATMEL_HLCDC_LAYER_CHDR 0x4
|
||||
#define ATMEL_HLCDC_LAYER_CHSR 0x8
|
||||
#define ATMEL_HLCDC_LAYER_DMA_CHAN 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 BIT(2)
|
||||
#define ATMEL_HLCDC_LAYER_DSCR_IRQ BIT(3)
|
||||
#define ATMEL_HLCDC_LAYER_ADD_IRQ BIT(4)
|
||||
#define ATMEL_HLCDC_LAYER_DONE_IRQ BIT(5)
|
||||
#define ATMEL_HLCDC_LAYER_OVR_IRQ BIT(6)
|
||||
|
||||
#define ATMEL_HLCDC_LAYER_PLANE_HEAD(n) (((n) * 0x10) + 0x1c)
|
||||
#define ATMEL_HLCDC_LAYER_PLANE_ADDR(n) (((n) * 0x10) + 0x20)
|
||||
#define ATMEL_HLCDC_LAYER_PLANE_CTRL(n) (((n) * 0x10) + 0x24)
|
||||
#define ATMEL_HLCDC_LAYER_PLANE_NEXT(n) (((n) * 0x10) + 0x28)
|
||||
#define ATMEL_HLCDC_LAYER_CFG(p, c) (((c) * 4) + ((p)->max_planes * 0x10) + 0x1c)
|
||||
|
||||
#define ATMEL_HLCDC_LAYER_DMA_CFG_ID 0
|
||||
#define ATMEL_HLCDC_LAYER_DMA_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, ATMEL_HLCDC_LAYER_DMA_CFG_ID)
|
||||
#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_ID 1
|
||||
#define ATMEL_HLCDC_LAYER_FORMAT_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, ATMEL_HLCDC_LAYER_FORMAT_CFG_ID)
|
||||
#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) (((m) & 0xf) << 4)
|
||||
#define ATMEL_HLCDC_CLUT_MODE(m) (((m) & 0x3) << 8)
|
||||
#define ATMEL_HLCDC_YUV_MODE(m) (((m) & 0xf) << 12)
|
||||
#define ATMEL_HLCDC_YUV422ROT BIT(16)
|
||||
#define ATMEL_HLCDC_YUV422SWP BIT(17)
|
||||
#define ATMEL_HLCDC_DSCALEOPT BIT(20)
|
||||
|
||||
#define ATMEL_HLCDC_XRGB4444_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(0))
|
||||
#define ATMEL_HLCDC_ARGB4444_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(1))
|
||||
#define ATMEL_HLCDC_RGBA4444_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(2))
|
||||
#define ATMEL_HLCDC_RGB565_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(3))
|
||||
#define ATMEL_HLCDC_ARGB1555_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(4))
|
||||
#define ATMEL_HLCDC_XRGB8888_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(9))
|
||||
#define ATMEL_HLCDC_RGB888_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(10))
|
||||
#define ATMEL_HLCDC_ARGB8888_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(12))
|
||||
#define ATMEL_HLCDC_RGBA8888_MODE (ATMEL_HLCDC_LAYER_RGB | ATMEL_HLCDC_RGB_MODE(13))
|
||||
|
||||
#define ATMEL_HLCDC_AYUV_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(0))
|
||||
#define ATMEL_HLCDC_YUYV_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(1))
|
||||
#define ATMEL_HLCDC_UYVY_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(2))
|
||||
#define ATMEL_HLCDC_YVYU_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(3))
|
||||
#define ATMEL_HLCDC_VYUY_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(4))
|
||||
#define ATMEL_HLCDC_NV61_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(5))
|
||||
#define ATMEL_HLCDC_YUV422_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(6))
|
||||
#define ATMEL_HLCDC_NV21_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(7))
|
||||
#define ATMEL_HLCDC_YUV420_MODE (ATMEL_HLCDC_LAYER_YUV | ATMEL_HLCDC_YUV_MODE(8))
|
||||
|
||||
#define ATMEL_HLCDC_LAYER_POS_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.pos)
|
||||
#define ATMEL_HLCDC_LAYER_SIZE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.size)
|
||||
#define ATMEL_HLCDC_LAYER_MEMSIZE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.memsize)
|
||||
#define ATMEL_HLCDC_LAYER_XSTRIDE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.xstride)
|
||||
#define ATMEL_HLCDC_LAYER_PSTRIDE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.pstride)
|
||||
#define ATMEL_HLCDC_LAYER_DFLTCOLOR_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.default_color)
|
||||
#define ATMEL_HLCDC_LAYER_CRKEY_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.chroma_key)
|
||||
#define ATMEL_HLCDC_LAYER_CRKEY_MASK_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.chroma_key_mask)
|
||||
|
||||
#define ATMEL_HLCDC_LAYER_GENERAL_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.general_config)
|
||||
#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_CSC_CFG(p, o) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.csc + o)
|
||||
|
||||
#define ATMEL_HLCDC_LAYER_DISC_POS_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.disc_pos)
|
||||
|
||||
#define ATMEL_HLCDC_LAYER_DISC_SIZE_CFG(p) ATMEL_HLCDC_LAYER_CFG(p, (p)->desc->layout.disc_size)
|
||||
|
||||
#define ATMEL_HLCDC_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)
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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_MAX_PLANES];
|
||||
int pstride[ATMEL_HLCDC_MAX_PLANES];
|
||||
int pos;
|
||||
int size;
|
||||
int memsize;
|
||||
int default_color;
|
||||
int chroma_key;
|
||||
int chroma_key_mask;
|
||||
int general_config;
|
||||
int disc_pos;
|
||||
int disc_size;
|
||||
int csc;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC framebuffer flip structure
|
||||
*
|
||||
* This structure is allocated when someone asked for a layer update (most
|
||||
* likely a DRM plane update, either primary, overlay or cursor plane) and
|
||||
* released when the layer do not need to reference the framebuffer object
|
||||
* anymore (i.e. the layer was disabled or updated).
|
||||
*
|
||||
* @dscrs: DMA descriptors
|
||||
* @fb: the referenced framebuffer object
|
||||
* @ngems: number of GEM objects referenced by the fb element
|
||||
* @status: fb flip operation status
|
||||
*/
|
||||
struct atmel_hlcdc_layer_fb_flip {
|
||||
struct atmel_hlcdc_dma_channel_dscr *dscrs[ATMEL_HLCDC_MAX_PLANES];
|
||||
struct drm_flip_task *task;
|
||||
struct drm_framebuffer *fb;
|
||||
int ngems;
|
||||
u32 status;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @gem_flip: the attached gem_flip operation
|
||||
*/
|
||||
struct atmel_hlcdc_dma_channel_dscr {
|
||||
dma_addr_t addr;
|
||||
u32 ctrl;
|
||||
dma_addr_t next;
|
||||
u32 status;
|
||||
} __aligned(sizeof(u64));
|
||||
|
||||
/**
|
||||
* Atmel HLCDC layer types
|
||||
*/
|
||||
enum atmel_hlcdc_layer_type {
|
||||
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;
|
||||
uint32_t *formats;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Layer description structure
|
||||
*
|
||||
* This structure describe 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
|
||||
* @nconfigs: number of config registers provided by this layer
|
||||
* @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 nconfigs;
|
||||
struct atmel_hlcdc_formats *formats;
|
||||
struct atmel_hlcdc_layer_cfg_layout layout;
|
||||
int max_width;
|
||||
int max_height;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Layer Update Slot structure
|
||||
*
|
||||
* This structure stores layer update requests to be applied on next frame.
|
||||
* This is the base structure behind the atomic layer update infrastructure.
|
||||
*
|
||||
* Atomic layer update provides a way to update all layer's parameters
|
||||
* simultaneously. This is needed to avoid incompatible sequential updates
|
||||
* like this one:
|
||||
* 1) update layer format from RGB888 (1 plane/buffer) to YUV422
|
||||
* (2 planes/buffers)
|
||||
* 2) the format update is applied but the DMA channel for the second
|
||||
* plane/buffer is not enabled
|
||||
* 3) enable the DMA channel for the second plane
|
||||
*
|
||||
* @fb_flip: fb_flip object
|
||||
* @updated_configs: bitmask used to record modified configs
|
||||
* @configs: new config values
|
||||
*/
|
||||
struct atmel_hlcdc_layer_update_slot {
|
||||
struct atmel_hlcdc_layer_fb_flip *fb_flip;
|
||||
unsigned long *updated_configs;
|
||||
u32 *configs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Layer Update structure
|
||||
*
|
||||
* This structure provides a way to queue layer update requests.
|
||||
*
|
||||
* At a given time there is at most:
|
||||
* - one pending update request, which means the update request has been
|
||||
* committed (or validated) and is waiting for the DMA channel(s) to be
|
||||
* available
|
||||
* - one request being prepared, which means someone started a layer update
|
||||
* but has not committed it yet. There cannot be more than one started
|
||||
* request, because the update lock is taken when starting a layer update
|
||||
* and release when committing or rolling back the request.
|
||||
*
|
||||
* @slots: update slots. One is used for pending request and the other one
|
||||
* for started update request
|
||||
* @pending: the pending slot index or -1 if no request is pending
|
||||
* @next: the started update slot index or -1 no update has been started
|
||||
*/
|
||||
struct atmel_hlcdc_layer_update {
|
||||
struct atmel_hlcdc_layer_update_slot slots[2];
|
||||
int pending;
|
||||
int next;
|
||||
};
|
||||
|
||||
enum atmel_hlcdc_layer_dma_channel_status {
|
||||
ATMEL_HLCDC_LAYER_DISABLED,
|
||||
ATMEL_HLCDC_LAYER_ENABLED,
|
||||
ATMEL_HLCDC_LAYER_DISABLING,
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Layer DMA channel structure
|
||||
*
|
||||
* This structure stores information on the DMA channel associated to a
|
||||
* given layer.
|
||||
*
|
||||
* @status: DMA channel status
|
||||
* @cur: current framebuffer
|
||||
* @queue: next framebuffer
|
||||
* @dscrs: allocated DMA descriptors
|
||||
*/
|
||||
struct atmel_hlcdc_layer_dma_channel {
|
||||
enum atmel_hlcdc_layer_dma_channel_status status;
|
||||
struct atmel_hlcdc_layer_fb_flip *cur;
|
||||
struct atmel_hlcdc_layer_fb_flip *queue;
|
||||
struct atmel_hlcdc_dma_channel_dscr *dscrs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Layer structure
|
||||
*
|
||||
* This structure stores information on the layer instance.
|
||||
*
|
||||
* @desc: layer description
|
||||
* @max_planes: maximum planes/buffers that can be associated with this layer.
|
||||
* This depends on the supported formats.
|
||||
* @hlcdc: pointer to the atmel_hlcdc structure provided by the MFD device
|
||||
* @dma: dma channel
|
||||
* @gc: fb flip garbage collector
|
||||
* @update: update handler
|
||||
* @lock: layer lock
|
||||
*/
|
||||
struct atmel_hlcdc_layer {
|
||||
const struct atmel_hlcdc_layer_desc *desc;
|
||||
int max_planes;
|
||||
struct atmel_hlcdc *hlcdc;
|
||||
struct workqueue_struct *wq;
|
||||
struct drm_flip_work gc;
|
||||
struct atmel_hlcdc_layer_dma_channel dma;
|
||||
struct atmel_hlcdc_layer_update update;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
void atmel_hlcdc_layer_irq(struct atmel_hlcdc_layer *layer);
|
||||
|
||||
int atmel_hlcdc_layer_init(struct drm_device *dev,
|
||||
struct atmel_hlcdc_layer *layer,
|
||||
const struct atmel_hlcdc_layer_desc *desc);
|
||||
|
||||
void atmel_hlcdc_layer_cleanup(struct drm_device *dev,
|
||||
struct atmel_hlcdc_layer *layer);
|
||||
|
||||
int atmel_hlcdc_layer_disable(struct atmel_hlcdc_layer *layer);
|
||||
|
||||
int atmel_hlcdc_layer_update_start(struct atmel_hlcdc_layer *layer);
|
||||
|
||||
void atmel_hlcdc_layer_update_cfg(struct atmel_hlcdc_layer *layer, int cfg,
|
||||
u32 mask, u32 val);
|
||||
|
||||
void atmel_hlcdc_layer_update_set_fb(struct atmel_hlcdc_layer *layer,
|
||||
struct drm_framebuffer *fb,
|
||||
unsigned int *offsets);
|
||||
|
||||
void atmel_hlcdc_layer_update_set_finished(struct atmel_hlcdc_layer *layer,
|
||||
void (*finished)(void *data),
|
||||
void *finished_data);
|
||||
|
||||
void atmel_hlcdc_layer_update_rollback(struct atmel_hlcdc_layer *layer);
|
||||
|
||||
void atmel_hlcdc_layer_update_commit(struct atmel_hlcdc_layer *layer);
|
||||
|
||||
#endif /* DRM_ATMEL_HLCDC_LAYER_H */
|
319
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
Normal file
319
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_output.c
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* 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>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <linux/of_graph.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include "atmel_hlcdc_dc.h"
|
||||
|
||||
/**
|
||||
* Atmel HLCDC RGB output mode
|
||||
*/
|
||||
enum atmel_hlcdc_connector_rgb_mode {
|
||||
ATMEL_HLCDC_CONNECTOR_RGB444,
|
||||
ATMEL_HLCDC_CONNECTOR_RGB565,
|
||||
ATMEL_HLCDC_CONNECTOR_RGB666,
|
||||
ATMEL_HLCDC_CONNECTOR_RGB888,
|
||||
};
|
||||
|
||||
/**
|
||||
* Atmel HLCDC RGB connector structure
|
||||
*
|
||||
* This structure stores RGB slave device information.
|
||||
*
|
||||
* @connector: DRM connector
|
||||
* @encoder: DRM encoder
|
||||
* @dc: pointer to the atmel_hlcdc_dc structure
|
||||
* @dpms: current DPMS mode
|
||||
*/
|
||||
struct atmel_hlcdc_rgb_output {
|
||||
struct drm_connector connector;
|
||||
struct drm_encoder encoder;
|
||||
struct atmel_hlcdc_dc *dc;
|
||||
int dpms;
|
||||
};
|
||||
|
||||
static inline struct atmel_hlcdc_rgb_output *
|
||||
drm_connector_to_atmel_hlcdc_rgb_output(struct drm_connector *connector)
|
||||
{
|
||||
return container_of(connector, struct atmel_hlcdc_rgb_output,
|
||||
connector);
|
||||
}
|
||||
|
||||
static inline struct atmel_hlcdc_rgb_output *
|
||||
drm_encoder_to_atmel_hlcdc_rgb_output(struct drm_encoder *encoder)
|
||||
{
|
||||
return container_of(encoder, struct atmel_hlcdc_rgb_output, encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atmel HLCDC Panel device structure
|
||||
*
|
||||
* This structure is specialization of the slave device structure to
|
||||
* interface with drm panels.
|
||||
*
|
||||
* @base: base slave device fields
|
||||
* @panel: drm panel attached to this slave device
|
||||
*/
|
||||
struct atmel_hlcdc_panel {
|
||||
struct atmel_hlcdc_rgb_output base;
|
||||
struct drm_panel *panel;
|
||||
};
|
||||
|
||||
static inline struct atmel_hlcdc_panel *
|
||||
atmel_hlcdc_rgb_output_to_panel(struct atmel_hlcdc_rgb_output *output)
|
||||
{
|
||||
return container_of(output, struct atmel_hlcdc_panel, base);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_panel_encoder_dpms(struct drm_encoder *encoder,
|
||||
int mode)
|
||||
{
|
||||
struct atmel_hlcdc_rgb_output *rgb =
|
||||
drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
|
||||
struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);
|
||||
|
||||
if (mode != DRM_MODE_DPMS_ON)
|
||||
mode = DRM_MODE_DPMS_OFF;
|
||||
|
||||
if (mode == rgb->dpms)
|
||||
return;
|
||||
|
||||
if (mode != DRM_MODE_DPMS_ON)
|
||||
drm_panel_disable(panel->panel);
|
||||
else
|
||||
drm_panel_enable(panel->panel);
|
||||
|
||||
rgb->dpms = mode;
|
||||
}
|
||||
|
||||
static bool
|
||||
atmel_hlcdc_panel_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_panel_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_panel_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
atmel_hlcdc_panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static void
|
||||
atmel_hlcdc_rgb_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted)
|
||||
{
|
||||
struct atmel_hlcdc_rgb_output *rgb =
|
||||
drm_encoder_to_atmel_hlcdc_rgb_output(encoder);
|
||||
struct drm_display_info *info = &rgb->connector.display_info;
|
||||
unsigned int cfg;
|
||||
|
||||
cfg = 0;
|
||||
|
||||
if (info->num_bus_formats) {
|
||||
switch (info->bus_formats[0]) {
|
||||
case MEDIA_BUS_FMT_RGB666_1X18:
|
||||
cfg |= ATMEL_HLCDC_CONNECTOR_RGB666 << 8;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
cfg |= ATMEL_HLCDC_CONNECTOR_RGB888 << 8;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
regmap_update_bits(rgb->dc->hlcdc->regmap, ATMEL_HLCDC_CFG(5),
|
||||
ATMEL_HLCDC_MODE_MASK,
|
||||
cfg);
|
||||
}
|
||||
|
||||
static struct drm_encoder_helper_funcs atmel_hlcdc_panel_encoder_helper_funcs = {
|
||||
.dpms = atmel_hlcdc_panel_encoder_dpms,
|
||||
.mode_fixup = atmel_hlcdc_panel_encoder_mode_fixup,
|
||||
.prepare = atmel_hlcdc_panel_encoder_prepare,
|
||||
.commit = atmel_hlcdc_panel_encoder_commit,
|
||||
.mode_set = atmel_hlcdc_rgb_encoder_mode_set,
|
||||
};
|
||||
|
||||
static void atmel_hlcdc_rgb_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
drm_encoder_cleanup(encoder);
|
||||
memset(encoder, 0, sizeof(*encoder));
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs atmel_hlcdc_panel_encoder_funcs = {
|
||||
.destroy = atmel_hlcdc_rgb_encoder_destroy,
|
||||
};
|
||||
|
||||
static int atmel_hlcdc_panel_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct atmel_hlcdc_rgb_output *rgb =
|
||||
drm_connector_to_atmel_hlcdc_rgb_output(connector);
|
||||
struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);
|
||||
|
||||
return panel->panel->funcs->get_modes(panel->panel);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_rgb_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct atmel_hlcdc_rgb_output *rgb =
|
||||
drm_connector_to_atmel_hlcdc_rgb_output(connector);
|
||||
|
||||
return atmel_hlcdc_dc_mode_valid(rgb->dc, mode);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static struct drm_encoder *
|
||||
atmel_hlcdc_rgb_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct atmel_hlcdc_rgb_output *rgb =
|
||||
drm_connector_to_atmel_hlcdc_rgb_output(connector);
|
||||
|
||||
return &rgb->encoder;
|
||||
}
|
||||
|
||||
static struct drm_connector_helper_funcs atmel_hlcdc_panel_connector_helper_funcs = {
|
||||
.get_modes = atmel_hlcdc_panel_get_modes,
|
||||
.mode_valid = atmel_hlcdc_rgb_mode_valid,
|
||||
.best_encoder = atmel_hlcdc_rgb_best_encoder,
|
||||
};
|
||||
|
||||
static enum drm_connector_status
|
||||
atmel_hlcdc_panel_connector_detect(struct drm_connector *connector, bool force)
|
||||
{
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
static void
|
||||
atmel_hlcdc_panel_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct atmel_hlcdc_rgb_output *rgb =
|
||||
drm_connector_to_atmel_hlcdc_rgb_output(connector);
|
||||
struct atmel_hlcdc_panel *panel = atmel_hlcdc_rgb_output_to_panel(rgb);
|
||||
|
||||
drm_panel_detach(panel->panel);
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs atmel_hlcdc_panel_connector_funcs = {
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.detect = atmel_hlcdc_panel_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = atmel_hlcdc_panel_connector_destroy,
|
||||
};
|
||||
|
||||
static int atmel_hlcdc_create_panel_output(struct drm_device *dev,
|
||||
struct of_endpoint *ep)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
struct device_node *np;
|
||||
struct drm_panel *p = NULL;
|
||||
struct atmel_hlcdc_panel *panel;
|
||||
int ret;
|
||||
|
||||
np = of_graph_get_remote_port_parent(ep->local_node);
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
|
||||
p = of_drm_find_panel(np);
|
||||
of_node_put(np);
|
||||
|
||||
if (!p)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
panel = devm_kzalloc(dev->dev, sizeof(*panel), GFP_KERNEL);
|
||||
if (!panel)
|
||||
return -EINVAL;
|
||||
|
||||
panel->base.dpms = DRM_MODE_DPMS_OFF;
|
||||
|
||||
panel->base.dc = dc;
|
||||
|
||||
drm_encoder_helper_add(&panel->base.encoder,
|
||||
&atmel_hlcdc_panel_encoder_helper_funcs);
|
||||
ret = drm_encoder_init(dev, &panel->base.encoder,
|
||||
&atmel_hlcdc_panel_encoder_funcs,
|
||||
DRM_MODE_ENCODER_LVDS);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
panel->base.connector.dpms = DRM_MODE_DPMS_OFF;
|
||||
panel->base.connector.polled = DRM_CONNECTOR_POLL_CONNECT;
|
||||
drm_connector_helper_add(&panel->base.connector,
|
||||
&atmel_hlcdc_panel_connector_helper_funcs);
|
||||
ret = drm_connector_init(dev, &panel->base.connector,
|
||||
&atmel_hlcdc_panel_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
if (ret)
|
||||
goto err_encoder_cleanup;
|
||||
|
||||
drm_mode_connector_attach_encoder(&panel->base.connector,
|
||||
&panel->base.encoder);
|
||||
panel->base.encoder.possible_crtcs = 0x1;
|
||||
|
||||
drm_panel_attach(p, &panel->base.connector);
|
||||
panel->panel = p;
|
||||
|
||||
return 0;
|
||||
|
||||
err_encoder_cleanup:
|
||||
drm_encoder_cleanup(&panel->base.encoder);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int atmel_hlcdc_create_outputs(struct drm_device *dev)
|
||||
{
|
||||
struct device_node *port_np, *np;
|
||||
struct of_endpoint ep;
|
||||
int ret;
|
||||
|
||||
port_np = of_get_child_by_name(dev->dev->of_node, "port");
|
||||
if (!port_np)
|
||||
return -EINVAL;
|
||||
|
||||
np = of_get_child_by_name(port_np, "endpoint");
|
||||
of_node_put(port_np);
|
||||
|
||||
if (!np)
|
||||
return -EINVAL;
|
||||
|
||||
ret = of_graph_parse_endpoint(np, &ep);
|
||||
of_node_put(port_np);
|
||||
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* We currently only support panel output */
|
||||
return atmel_hlcdc_create_panel_output(dev, &ep);
|
||||
}
|
856
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
Normal file
856
drivers/gpu/drm/atmel-hlcdc/atmel_hlcdc_plane.c
Normal file
@ -0,0 +1,856 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Free Electrons
|
||||
* Copyright (C) 2014 Atmel
|
||||
*
|
||||
* Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 as published by
|
||||
* the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "atmel_hlcdc_dc.h"
|
||||
|
||||
#define SUBPIXEL_MASK 0xffff
|
||||
|
||||
static uint32_t rgb_formats[] = {
|
||||
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_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_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 bool atmel_hlcdc_format_embedds_alpha(u32 format)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sizeof(format); i++) {
|
||||
char tmp = (format >> (8 * i)) & 0xff;
|
||||
|
||||
if (tmp == 'A')
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
static void
|
||||
atmel_hlcdc_plane_update_pos_and_size(struct atmel_hlcdc_plane *plane,
|
||||
struct atmel_hlcdc_plane_update_req *req)
|
||||
{
|
||||
const struct atmel_hlcdc_layer_cfg_layout *layout =
|
||||
&plane->layer.desc->layout;
|
||||
|
||||
if (layout->size)
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
layout->size,
|
||||
0xffffffff,
|
||||
(req->crtc_w - 1) |
|
||||
((req->crtc_h - 1) << 16));
|
||||
|
||||
if (layout->memsize)
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
layout->memsize,
|
||||
0xffffffff,
|
||||
(req->src_w - 1) |
|
||||
((req->src_h - 1) << 16));
|
||||
|
||||
if (layout->pos)
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
layout->pos,
|
||||
0xffffffff,
|
||||
req->crtc_x |
|
||||
(req->crtc_y << 16));
|
||||
|
||||
/* TODO: rework the rescaling part */
|
||||
if (req->crtc_w != req->src_w || req->crtc_h != req->src_h) {
|
||||
u32 factor_reg = 0;
|
||||
|
||||
if (req->crtc_w != req->src_w) {
|
||||
int i;
|
||||
u32 factor;
|
||||
u32 *coeff_tab = heo_upscaling_xcoef;
|
||||
u32 max_memsize;
|
||||
|
||||
if (req->crtc_w < req->src_w)
|
||||
coeff_tab = heo_downscaling_xcoef;
|
||||
for (i = 0; i < ARRAY_SIZE(heo_upscaling_xcoef); i++)
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
17 + i,
|
||||
0xffffffff,
|
||||
coeff_tab[i]);
|
||||
factor = ((8 * 256 * req->src_w) - (256 * 4)) /
|
||||
req->crtc_w;
|
||||
factor++;
|
||||
max_memsize = ((factor * req->crtc_w) + (256 * 4)) /
|
||||
2048;
|
||||
if (max_memsize > req->src_w)
|
||||
factor--;
|
||||
factor_reg |= factor | 0x80000000;
|
||||
}
|
||||
|
||||
if (req->crtc_h != req->src_h) {
|
||||
int i;
|
||||
u32 factor;
|
||||
u32 *coeff_tab = heo_upscaling_ycoef;
|
||||
u32 max_memsize;
|
||||
|
||||
if (req->crtc_w < req->src_w)
|
||||
coeff_tab = heo_downscaling_ycoef;
|
||||
for (i = 0; i < ARRAY_SIZE(heo_upscaling_ycoef); i++)
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
33 + i,
|
||||
0xffffffff,
|
||||
coeff_tab[i]);
|
||||
factor = ((8 * 256 * req->src_w) - (256 * 4)) /
|
||||
req->crtc_w;
|
||||
factor++;
|
||||
max_memsize = ((factor * req->crtc_w) + (256 * 4)) /
|
||||
2048;
|
||||
if (max_memsize > req->src_w)
|
||||
factor--;
|
||||
factor_reg |= (factor << 16) | 0x80000000;
|
||||
}
|
||||
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer, 13, 0xffffffff,
|
||||
factor_reg);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
atmel_hlcdc_plane_update_general_settings(struct atmel_hlcdc_plane *plane,
|
||||
struct atmel_hlcdc_plane_update_req *req)
|
||||
{
|
||||
const struct atmel_hlcdc_layer_cfg_layout *layout =
|
||||
&plane->layer.desc->layout;
|
||||
unsigned int cfg = ATMEL_HLCDC_LAYER_DMA;
|
||||
|
||||
if (plane->base.type != DRM_PLANE_TYPE_PRIMARY) {
|
||||
cfg |= ATMEL_HLCDC_LAYER_OVR | ATMEL_HLCDC_LAYER_ITER2BL |
|
||||
ATMEL_HLCDC_LAYER_ITER;
|
||||
|
||||
if (atmel_hlcdc_format_embedds_alpha(req->fb->pixel_format))
|
||||
cfg |= ATMEL_HLCDC_LAYER_LAEN;
|
||||
else
|
||||
cfg |= ATMEL_HLCDC_LAYER_GAEN;
|
||||
}
|
||||
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
ATMEL_HLCDC_LAYER_DMA_CFG_ID,
|
||||
ATMEL_HLCDC_LAYER_DMA_BLEN_MASK,
|
||||
ATMEL_HLCDC_LAYER_DMA_BLEN_INCR16);
|
||||
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer, layout->general_config,
|
||||
ATMEL_HLCDC_LAYER_ITER2BL |
|
||||
ATMEL_HLCDC_LAYER_ITER |
|
||||
ATMEL_HLCDC_LAYER_GAEN |
|
||||
ATMEL_HLCDC_LAYER_LAEN |
|
||||
ATMEL_HLCDC_LAYER_OVR |
|
||||
ATMEL_HLCDC_LAYER_DMA, cfg);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_plane_update_format(struct atmel_hlcdc_plane *plane,
|
||||
struct atmel_hlcdc_plane_update_req *req)
|
||||
{
|
||||
u32 cfg;
|
||||
int ret;
|
||||
|
||||
ret = atmel_hlcdc_format_to_plane_mode(req->fb->pixel_format, &cfg);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
if ((req->fb->pixel_format == DRM_FORMAT_YUV422 ||
|
||||
req->fb->pixel_format == DRM_FORMAT_NV61) &&
|
||||
(plane->rotation & (BIT(DRM_ROTATE_90) | BIT(DRM_ROTATE_270))))
|
||||
cfg |= ATMEL_HLCDC_YUV422ROT;
|
||||
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
ATMEL_HLCDC_LAYER_FORMAT_CFG_ID,
|
||||
0xffffffff,
|
||||
cfg);
|
||||
|
||||
/*
|
||||
* Rotation optimization is not working on RGB888 (rotation is still
|
||||
* working but without any optimization).
|
||||
*/
|
||||
if (req->fb->pixel_format == DRM_FORMAT_RGB888)
|
||||
cfg = ATMEL_HLCDC_LAYER_DMA_ROTDIS;
|
||||
else
|
||||
cfg = 0;
|
||||
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
ATMEL_HLCDC_LAYER_DMA_CFG_ID,
|
||||
ATMEL_HLCDC_LAYER_DMA_ROTDIS, cfg);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_plane_update_buffers(struct atmel_hlcdc_plane *plane,
|
||||
struct atmel_hlcdc_plane_update_req *req)
|
||||
{
|
||||
struct atmel_hlcdc_layer *layer = &plane->layer;
|
||||
const struct atmel_hlcdc_layer_cfg_layout *layout =
|
||||
&layer->desc->layout;
|
||||
int i;
|
||||
|
||||
atmel_hlcdc_layer_update_set_fb(&plane->layer, req->fb, req->offsets);
|
||||
|
||||
for (i = 0; i < req->nplanes; i++) {
|
||||
if (layout->xstride[i]) {
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
layout->xstride[i],
|
||||
0xffffffff,
|
||||
req->xstride[i]);
|
||||
}
|
||||
|
||||
if (layout->pstride[i]) {
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
layout->pstride[i],
|
||||
0xffffffff,
|
||||
req->pstride[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_plane_check_update_req(struct drm_plane *p,
|
||||
struct atmel_hlcdc_plane_update_req *req,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
||||
const struct atmel_hlcdc_layer_cfg_layout *layout =
|
||||
&plane->layer.desc->layout;
|
||||
|
||||
if (!layout->size &&
|
||||
(mode->hdisplay != req->crtc_w ||
|
||||
mode->vdisplay != req->crtc_h))
|
||||
return -EINVAL;
|
||||
|
||||
if (plane->layer.desc->max_height &&
|
||||
req->crtc_h > plane->layer.desc->max_height)
|
||||
return -EINVAL;
|
||||
|
||||
if (plane->layer.desc->max_width &&
|
||||
req->crtc_w > plane->layer.desc->max_width)
|
||||
return -EINVAL;
|
||||
|
||||
if ((req->crtc_h != req->src_h || req->crtc_w != req->src_w) &&
|
||||
(!layout->memsize ||
|
||||
atmel_hlcdc_format_embedds_alpha(req->fb->pixel_format)))
|
||||
return -EINVAL;
|
||||
|
||||
if (req->crtc_x < 0 || req->crtc_y < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (req->crtc_w + req->crtc_x > mode->hdisplay ||
|
||||
req->crtc_h + req->crtc_y > mode->vdisplay)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int atmel_hlcdc_plane_prepare_update_req(struct drm_plane *p,
|
||||
struct atmel_hlcdc_plane_update_req *req,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
||||
unsigned int patched_crtc_w;
|
||||
unsigned int patched_crtc_h;
|
||||
unsigned int patched_src_w;
|
||||
unsigned int patched_src_h;
|
||||
unsigned int tmp;
|
||||
int x_offset = 0;
|
||||
int y_offset = 0;
|
||||
int hsub = 1;
|
||||
int vsub = 1;
|
||||
int i;
|
||||
|
||||
if ((req->src_x | req->src_y | req->src_w | req->src_h) &
|
||||
SUBPIXEL_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
req->src_x >>= 16;
|
||||
req->src_y >>= 16;
|
||||
req->src_w >>= 16;
|
||||
req->src_h >>= 16;
|
||||
|
||||
req->nplanes = drm_format_num_planes(req->fb->pixel_format);
|
||||
if (req->nplanes > ATMEL_HLCDC_MAX_PLANES)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Swap width and size in case of 90 or 270 degrees rotation
|
||||
*/
|
||||
if (plane->rotation & (BIT(DRM_ROTATE_90) | BIT(DRM_ROTATE_270))) {
|
||||
tmp = req->crtc_w;
|
||||
req->crtc_w = req->crtc_h;
|
||||
req->crtc_h = tmp;
|
||||
tmp = req->src_w;
|
||||
req->src_w = req->src_h;
|
||||
req->src_h = tmp;
|
||||
}
|
||||
|
||||
if (req->crtc_x + req->crtc_w > mode->hdisplay)
|
||||
patched_crtc_w = mode->hdisplay - req->crtc_x;
|
||||
else
|
||||
patched_crtc_w = req->crtc_w;
|
||||
|
||||
if (req->crtc_x < 0) {
|
||||
patched_crtc_w += req->crtc_x;
|
||||
x_offset = -req->crtc_x;
|
||||
req->crtc_x = 0;
|
||||
}
|
||||
|
||||
if (req->crtc_y + req->crtc_h > mode->vdisplay)
|
||||
patched_crtc_h = mode->vdisplay - req->crtc_y;
|
||||
else
|
||||
patched_crtc_h = req->crtc_h;
|
||||
|
||||
if (req->crtc_y < 0) {
|
||||
patched_crtc_h += req->crtc_y;
|
||||
y_offset = -req->crtc_y;
|
||||
req->crtc_y = 0;
|
||||
}
|
||||
|
||||
patched_src_w = DIV_ROUND_CLOSEST(patched_crtc_w * req->src_w,
|
||||
req->crtc_w);
|
||||
patched_src_h = DIV_ROUND_CLOSEST(patched_crtc_h * req->src_h,
|
||||
req->crtc_h);
|
||||
|
||||
hsub = drm_format_horz_chroma_subsampling(req->fb->pixel_format);
|
||||
vsub = drm_format_vert_chroma_subsampling(req->fb->pixel_format);
|
||||
|
||||
for (i = 0; i < req->nplanes; i++) {
|
||||
unsigned int offset = 0;
|
||||
int xdiv = i ? hsub : 1;
|
||||
int ydiv = i ? vsub : 1;
|
||||
|
||||
req->bpp[i] = drm_format_plane_cpp(req->fb->pixel_format, i);
|
||||
if (!req->bpp[i])
|
||||
return -EINVAL;
|
||||
|
||||
switch (plane->rotation & 0xf) {
|
||||
case BIT(DRM_ROTATE_90):
|
||||
offset = ((y_offset + req->src_y + patched_src_w - 1) /
|
||||
ydiv) * req->fb->pitches[i];
|
||||
offset += ((x_offset + req->src_x) / xdiv) *
|
||||
req->bpp[i];
|
||||
req->xstride[i] = ((patched_src_w - 1) / ydiv) *
|
||||
req->fb->pitches[i];
|
||||
req->pstride[i] = -req->fb->pitches[i] - req->bpp[i];
|
||||
break;
|
||||
case BIT(DRM_ROTATE_180):
|
||||
offset = ((y_offset + req->src_y + patched_src_h - 1) /
|
||||
ydiv) * req->fb->pitches[i];
|
||||
offset += ((x_offset + req->src_x + patched_src_w - 1) /
|
||||
xdiv) * req->bpp[i];
|
||||
req->xstride[i] = ((((patched_src_w - 1) / xdiv) - 1) *
|
||||
req->bpp[i]) - req->fb->pitches[i];
|
||||
req->pstride[i] = -2 * req->bpp[i];
|
||||
break;
|
||||
case BIT(DRM_ROTATE_270):
|
||||
offset = ((y_offset + req->src_y) / ydiv) *
|
||||
req->fb->pitches[i];
|
||||
offset += ((x_offset + req->src_x + patched_src_h - 1) /
|
||||
xdiv) * req->bpp[i];
|
||||
req->xstride[i] = -(((patched_src_w - 1) / ydiv) *
|
||||
req->fb->pitches[i]) -
|
||||
(2 * req->bpp[i]);
|
||||
req->pstride[i] = req->fb->pitches[i] - req->bpp[i];
|
||||
break;
|
||||
case BIT(DRM_ROTATE_0):
|
||||
default:
|
||||
offset = ((y_offset + req->src_y) / ydiv) *
|
||||
req->fb->pitches[i];
|
||||
offset += ((x_offset + req->src_x) / xdiv) *
|
||||
req->bpp[i];
|
||||
req->xstride[i] = req->fb->pitches[i] -
|
||||
((patched_src_w / xdiv) *
|
||||
req->bpp[i]);
|
||||
req->pstride[i] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
req->offsets[i] = offset + req->fb->offsets[i];
|
||||
}
|
||||
|
||||
req->src_w = patched_src_w;
|
||||
req->src_h = patched_src_h;
|
||||
req->crtc_w = patched_crtc_w;
|
||||
req->crtc_h = patched_crtc_h;
|
||||
|
||||
return atmel_hlcdc_plane_check_update_req(p, req, mode);
|
||||
}
|
||||
|
||||
int atmel_hlcdc_plane_apply_update_req(struct drm_plane *p,
|
||||
struct atmel_hlcdc_plane_update_req *req)
|
||||
{
|
||||
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
||||
int ret;
|
||||
|
||||
ret = atmel_hlcdc_layer_update_start(&plane->layer);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
atmel_hlcdc_plane_update_pos_and_size(plane, req);
|
||||
atmel_hlcdc_plane_update_general_settings(plane, req);
|
||||
atmel_hlcdc_plane_update_format(plane, req);
|
||||
atmel_hlcdc_plane_update_buffers(plane, req);
|
||||
|
||||
atmel_hlcdc_layer_update_commit(&plane->layer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int atmel_hlcdc_plane_update_with_mode(struct drm_plane *p,
|
||||
struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *fb,
|
||||
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,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
||||
struct atmel_hlcdc_plane_update_req req;
|
||||
int ret = 0;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.crtc_x = crtc_x;
|
||||
req.crtc_y = crtc_y;
|
||||
req.crtc_w = crtc_w;
|
||||
req.crtc_h = crtc_h;
|
||||
req.src_x = src_x;
|
||||
req.src_y = src_y;
|
||||
req.src_w = src_w;
|
||||
req.src_h = src_h;
|
||||
req.fb = fb;
|
||||
|
||||
ret = atmel_hlcdc_plane_prepare_update_req(&plane->base, &req, mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!req.crtc_h || !req.crtc_w)
|
||||
return atmel_hlcdc_layer_disable(&plane->layer);
|
||||
|
||||
return atmel_hlcdc_plane_apply_update_req(&plane->base, &req);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_plane_update(struct drm_plane *p,
|
||||
struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *fb,
|
||||
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)
|
||||
{
|
||||
return atmel_hlcdc_plane_update_with_mode(p, crtc, fb, crtc_x, crtc_y,
|
||||
crtc_w, crtc_h, src_x, src_y,
|
||||
src_w, src_h, &crtc->hwmode);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_plane_disable(struct drm_plane *p)
|
||||
{
|
||||
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
||||
|
||||
return atmel_hlcdc_layer_disable(&plane->layer);
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_plane_destroy(struct drm_plane *p)
|
||||
{
|
||||
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
||||
|
||||
if (plane->base.fb)
|
||||
drm_framebuffer_unreference(plane->base.fb);
|
||||
|
||||
atmel_hlcdc_layer_cleanup(p->dev, &plane->layer);
|
||||
|
||||
drm_plane_cleanup(p);
|
||||
devm_kfree(p->dev->dev, plane);
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_plane_set_alpha(struct atmel_hlcdc_plane *plane,
|
||||
u8 alpha)
|
||||
{
|
||||
atmel_hlcdc_layer_update_start(&plane->layer);
|
||||
atmel_hlcdc_layer_update_cfg(&plane->layer,
|
||||
plane->layer.desc->layout.general_config,
|
||||
ATMEL_HLCDC_LAYER_GA_MASK,
|
||||
alpha << ATMEL_HLCDC_LAYER_GA_SHIFT);
|
||||
atmel_hlcdc_layer_update_commit(&plane->layer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_plane_set_rotation(struct atmel_hlcdc_plane *plane,
|
||||
unsigned int rotation)
|
||||
{
|
||||
plane->rotation = rotation;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int atmel_hlcdc_plane_set_property(struct drm_plane *p,
|
||||
struct drm_property *property,
|
||||
uint64_t value)
|
||||
{
|
||||
struct atmel_hlcdc_plane *plane = drm_plane_to_atmel_hlcdc_plane(p);
|
||||
struct atmel_hlcdc_plane_properties *props = plane->properties;
|
||||
|
||||
if (property == props->alpha)
|
||||
atmel_hlcdc_plane_set_alpha(plane, value);
|
||||
else if (property == props->rotation)
|
||||
atmel_hlcdc_plane_set_rotation(plane, value);
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void atmel_hlcdc_plane_init_properties(struct atmel_hlcdc_plane *plane,
|
||||
const struct atmel_hlcdc_layer_desc *desc,
|
||||
struct atmel_hlcdc_plane_properties *props)
|
||||
{
|
||||
struct regmap *regmap = plane->layer.hlcdc->regmap;
|
||||
|
||||
if (desc->type == ATMEL_HLCDC_OVERLAY_LAYER ||
|
||||
desc->type == ATMEL_HLCDC_CURSOR_LAYER) {
|
||||
drm_object_attach_property(&plane->base.base,
|
||||
props->alpha, 255);
|
||||
|
||||
/* Set default alpha value */
|
||||
regmap_update_bits(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_GENERAL_CFG(&plane->layer),
|
||||
ATMEL_HLCDC_LAYER_GA_MASK,
|
||||
ATMEL_HLCDC_LAYER_GA_MASK);
|
||||
}
|
||||
|
||||
if (desc->layout.xstride && desc->layout.pstride)
|
||||
drm_object_attach_property(&plane->base.base,
|
||||
props->rotation,
|
||||
BIT(DRM_ROTATE_0));
|
||||
|
||||
if (desc->layout.csc) {
|
||||
/*
|
||||
* TODO: decare a "yuv-to-rgb-conv-factors" property to let
|
||||
* userspace modify these factors (using a BLOB property ?).
|
||||
*/
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 0),
|
||||
0x4c900091);
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 1),
|
||||
0x7a5f5090);
|
||||
regmap_write(regmap,
|
||||
desc->regs_offset +
|
||||
ATMEL_HLCDC_LAYER_CSC_CFG(&plane->layer, 2),
|
||||
0x40040890);
|
||||
}
|
||||
}
|
||||
|
||||
static struct drm_plane_funcs layer_plane_funcs = {
|
||||
.update_plane = atmel_hlcdc_plane_update,
|
||||
.disable_plane = atmel_hlcdc_plane_disable,
|
||||
.set_property = atmel_hlcdc_plane_set_property,
|
||||
.destroy = atmel_hlcdc_plane_destroy,
|
||||
};
|
||||
|
||||
static struct atmel_hlcdc_plane *
|
||||
atmel_hlcdc_plane_create(struct drm_device *dev,
|
||||
const struct atmel_hlcdc_layer_desc *desc,
|
||||
struct atmel_hlcdc_plane_properties *props)
|
||||
{
|
||||
struct atmel_hlcdc_plane *plane;
|
||||
enum drm_plane_type type;
|
||||
int ret;
|
||||
|
||||
plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL);
|
||||
if (!plane)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = atmel_hlcdc_layer_init(dev, &plane->layer, desc);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
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, type);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
/* Set default property values*/
|
||||
atmel_hlcdc_plane_init_properties(plane, desc, props);
|
||||
|
||||
return plane;
|
||||
}
|
||||
|
||||
static struct atmel_hlcdc_plane_properties *
|
||||
atmel_hlcdc_plane_create_properties(struct drm_device *dev)
|
||||
{
|
||||
struct atmel_hlcdc_plane_properties *props;
|
||||
|
||||
props = devm_kzalloc(dev->dev, sizeof(*props), GFP_KERNEL);
|
||||
if (!props)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
props->alpha = drm_property_create_range(dev, 0, "alpha", 0, 255);
|
||||
if (!props->alpha)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
props->rotation = drm_mode_create_rotation_property(dev,
|
||||
BIT(DRM_ROTATE_0) |
|
||||
BIT(DRM_ROTATE_90) |
|
||||
BIT(DRM_ROTATE_180) |
|
||||
BIT(DRM_ROTATE_270));
|
||||
if (!props->rotation)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
struct atmel_hlcdc_planes *
|
||||
atmel_hlcdc_create_planes(struct drm_device *dev)
|
||||
{
|
||||
struct atmel_hlcdc_dc *dc = dev->dev_private;
|
||||
struct atmel_hlcdc_plane_properties *props;
|
||||
struct atmel_hlcdc_planes *planes;
|
||||
const struct atmel_hlcdc_layer_desc *descs = dc->desc->layers;
|
||||
int nlayers = dc->desc->nlayers;
|
||||
int i;
|
||||
|
||||
planes = devm_kzalloc(dev->dev, sizeof(*planes), GFP_KERNEL);
|
||||
if (!planes)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (i = 0; i < nlayers; i++) {
|
||||
if (descs[i].type == ATMEL_HLCDC_OVERLAY_LAYER)
|
||||
planes->noverlays++;
|
||||
}
|
||||
|
||||
if (planes->noverlays) {
|
||||
planes->overlays = devm_kzalloc(dev->dev,
|
||||
planes->noverlays *
|
||||
sizeof(*planes->overlays),
|
||||
GFP_KERNEL);
|
||||
if (!planes->overlays)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
props = atmel_hlcdc_plane_create_properties(dev);
|
||||
if (IS_ERR(props))
|
||||
return ERR_CAST(props);
|
||||
|
||||
planes->noverlays = 0;
|
||||
for (i = 0; i < nlayers; i++) {
|
||||
struct atmel_hlcdc_plane *plane;
|
||||
|
||||
if (descs[i].type == ATMEL_HLCDC_PP_LAYER)
|
||||
continue;
|
||||
|
||||
plane = atmel_hlcdc_plane_create(dev, &descs[i], props);
|
||||
if (IS_ERR(plane))
|
||||
return ERR_CAST(plane);
|
||||
|
||||
plane->properties = props;
|
||||
|
||||
switch (descs[i].type) {
|
||||
case ATMEL_HLCDC_BASE_LAYER:
|
||||
if (planes->primary)
|
||||
return ERR_PTR(-EINVAL);
|
||||
planes->primary = plane;
|
||||
break;
|
||||
|
||||
case ATMEL_HLCDC_OVERLAY_LAYER:
|
||||
planes->overlays[planes->noverlays++] = plane;
|
||||
break;
|
||||
|
||||
case ATMEL_HLCDC_CURSOR_LAYER:
|
||||
if (planes->cursor)
|
||||
return ERR_PTR(-EINVAL);
|
||||
planes->cursor = plane;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return planes;
|
||||
}
|
@ -761,6 +761,40 @@ static void drm_mode_remove(struct drm_connector *connector,
|
||||
drm_mode_destroy(connector->dev, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_display_info_set_bus_formats - set the supported bus formats
|
||||
* @info: display info to store bus formats in
|
||||
* @fmts: array containing the supported bus formats
|
||||
* @nfmts: the number of entries in the fmts array
|
||||
*
|
||||
* Store the supported bus formats in display info structure.
|
||||
* See MEDIA_BUS_FMT_* definitions in include/uapi/linux/media-bus-format.h for
|
||||
* a full list of available formats.
|
||||
*/
|
||||
int drm_display_info_set_bus_formats(struct drm_display_info *info,
|
||||
const u32 *formats,
|
||||
unsigned int num_formats)
|
||||
{
|
||||
u32 *fmts = NULL;
|
||||
|
||||
if (!formats && num_formats)
|
||||
return -EINVAL;
|
||||
|
||||
if (formats && num_formats) {
|
||||
fmts = kmemdup(formats, sizeof(*formats) * num_formats,
|
||||
GFP_KERNEL);
|
||||
if (!formats)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
kfree(info->bus_formats);
|
||||
info->bus_formats = fmts;
|
||||
info->num_bus_formats = num_formats;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_display_info_set_bus_formats);
|
||||
|
||||
/**
|
||||
* drm_connector_get_cmdline_mode - reads the user's cmdline mode
|
||||
* @connector: connector to quwery
|
||||
@ -923,6 +957,7 @@ void drm_connector_cleanup(struct drm_connector *connector)
|
||||
ida_remove(&drm_connector_enum_list[connector->connector_type].ida,
|
||||
connector->connector_type_id);
|
||||
|
||||
kfree(connector->display_info.bus_formats);
|
||||
drm_mode_object_put(dev, &connector->base);
|
||||
kfree(connector->name);
|
||||
connector->name = NULL;
|
||||
|
@ -61,6 +61,8 @@ struct panel_desc {
|
||||
unsigned int disable;
|
||||
unsigned int unprepare;
|
||||
} delay;
|
||||
|
||||
u32 bus_format;
|
||||
};
|
||||
|
||||
struct panel_simple {
|
||||
@ -111,6 +113,9 @@ static int panel_simple_get_fixed_modes(struct panel_simple *panel)
|
||||
connector->display_info.bpc = panel->desc->bpc;
|
||||
connector->display_info.width_mm = panel->desc->size.width;
|
||||
connector->display_info.height_mm = panel->desc->size.height;
|
||||
if (panel->desc->bus_format)
|
||||
drm_display_info_set_bus_formats(&connector->display_info,
|
||||
&panel->desc->bus_format, 1);
|
||||
|
||||
return num;
|
||||
}
|
||||
@ -558,6 +563,7 @@ static const struct panel_desc foxlink_fl500wvr00_a0t = {
|
||||
.width = 108,
|
||||
.height = 65,
|
||||
},
|
||||
.bus_format = MEDIA_BUS_FMT_RGB888_1X24,
|
||||
};
|
||||
|
||||
static const struct drm_display_mode hannstar_hsd070pww1_mode = {
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <linux/idr.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/hdmi.h>
|
||||
#include <linux/media-bus-format.h>
|
||||
#include <uapi/drm/drm_mode.h>
|
||||
#include <uapi/drm/drm_fourcc.h>
|
||||
#include <drm/drm_modeset_lock.h>
|
||||
@ -139,6 +140,9 @@ struct drm_display_info {
|
||||
enum subpixel_order subpixel_order;
|
||||
u32 color_formats;
|
||||
|
||||
const u32 *bus_formats;
|
||||
unsigned int num_bus_formats;
|
||||
|
||||
/* Mask of supported hdmi deep color modes */
|
||||
u8 edid_hdmi_dc_modes;
|
||||
|
||||
@ -1282,6 +1286,10 @@ int drm_mode_connector_set_tile_property(struct drm_connector *connector);
|
||||
extern int drm_mode_connector_update_edid_property(struct drm_connector *connector,
|
||||
const struct edid *edid);
|
||||
|
||||
extern int drm_display_info_set_bus_formats(struct drm_display_info *info,
|
||||
const u32 *formats,
|
||||
unsigned int num_formats);
|
||||
|
||||
static inline bool drm_property_type_is(struct drm_property *property,
|
||||
uint32_t type)
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user