blob: 39677d96ca1069eeaf082c98e0434e61194abc7e [file] [log] [blame]
/*
* Copyright 2017-2018 NXP
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* 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.
*/
#include <drm/drmP.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_cma_helper.h>
#include <drm/drm_gem_cma_helper.h>
#include <linux/component.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <video/dpu.h>
#include "dpu-kms.h"
#include "dpu-plane.h"
#include "imx-drm.h"
struct dpu_crtc {
struct device *dev;
struct drm_crtc base;
struct imx_drm_crtc *imx_crtc;
struct dpu_constframe *cf;
struct dpu_disengcfg *dec;
struct dpu_extdst *ed;
struct dpu_framegen *fg;
struct dpu_tcon *tcon;
struct dpu_plane **plane;
unsigned int hw_plane_num;
unsigned int stream_id;
int vbl_irq;
int safety_shdld_irq;
int content_shdld_irq;
int dec_shdld_irq;
struct completion safety_shdld_done;
struct completion content_shdld_done;
struct completion dec_shdld_done;
};
struct dpu_crtc_state {
struct imx_crtc_state imx_crtc_state;
struct dpu_plane_state **dpu_plane_states;
};
static inline struct dpu_crtc_state *to_dpu_crtc_state(struct imx_crtc_state *s)
{
return container_of(s, struct dpu_crtc_state, imx_crtc_state);
}
static inline struct dpu_crtc *to_dpu_crtc(struct drm_crtc *crtc)
{
return container_of(crtc, struct dpu_crtc, base);
}
static inline struct dpu_plane_state **
alloc_dpu_plane_states(struct dpu_crtc *dpu_crtc)
{
struct dpu_plane_state **states;
states = kcalloc(dpu_crtc->hw_plane_num, sizeof(*states), GFP_KERNEL);
if (!states)
return ERR_PTR(-ENOMEM);
return states;
}
struct dpu_plane_state **
crtc_state_get_dpu_plane_states(struct drm_crtc_state *state)
{
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(state);
struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
return dcstate->dpu_plane_states;
}
static void dpu_crtc_enable(struct drm_crtc *crtc)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
struct dpu_plane_res *res = &dplane->grp->res;
struct dpu_extdst *plane_ed = res->ed[dplane->stream_id];
unsigned long ret;
drm_crtc_vblank_on(crtc);
enable_irq(dpu_crtc->safety_shdld_irq);
enable_irq(dpu_crtc->content_shdld_irq);
enable_irq(dpu_crtc->dec_shdld_irq);
framegen_enable_clock(dpu_crtc->fg);
extdst_pixengcfg_sync_trigger(plane_ed);
extdst_pixengcfg_sync_trigger(dpu_crtc->ed);
framegen_shdtokgen(dpu_crtc->fg);
framegen_enable(dpu_crtc->fg);
ret = wait_for_completion_timeout(&dpu_crtc->safety_shdld_done, HZ);
if (ret == 0)
dev_warn(dpu_crtc->dev,
"enable - wait for safety shdld done timeout\n");
ret = wait_for_completion_timeout(&dpu_crtc->content_shdld_done, HZ);
if (ret == 0)
dev_warn(dpu_crtc->dev,
"enable - wait for content shdld done timeout\n");
ret = wait_for_completion_timeout(&dpu_crtc->dec_shdld_done, HZ);
if (ret == 0)
dev_warn(dpu_crtc->dev,
"enable - wait for DEC shdld done timeout\n");
disable_irq(dpu_crtc->safety_shdld_irq);
disable_irq(dpu_crtc->content_shdld_irq);
disable_irq(dpu_crtc->dec_shdld_irq);
if (crtc->state->event) {
spin_lock_irq(&crtc->dev->event_lock);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
/*
* TKT320590:
* Turn TCON into operation mode later after the first dumb frame is
* generated by DPU. This makes DPR/PRG be able to evade the frame.
*/
framegen_wait_for_frame_counter_moving(dpu_crtc->fg);
tcon_set_operation_mode(dpu_crtc->tcon);
}
static void dpu_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
framegen_disable(dpu_crtc->fg);
framegen_wait_done(dpu_crtc->fg, &old_crtc_state->adjusted_mode);
framegen_disable_clock(dpu_crtc->fg);
WARN_ON(!crtc->state->event);
if (crtc->state->event) {
spin_lock_irq(&crtc->dev->event_lock);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
drm_crtc_vblank_off(crtc);
}
static void dpu_drm_crtc_reset(struct drm_crtc *crtc)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
struct imx_crtc_state *imx_crtc_state;
struct dpu_crtc_state *state;
if (crtc->state) {
__drm_atomic_helper_crtc_destroy_state(crtc->state);
imx_crtc_state = to_imx_crtc_state(crtc->state);
state = to_dpu_crtc_state(imx_crtc_state);
kfree(state->dpu_plane_states);
kfree(state);
crtc->state = NULL;
}
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (state) {
crtc->state = &state->imx_crtc_state.base;
crtc->state->crtc = crtc;
state->dpu_plane_states = alloc_dpu_plane_states(dpu_crtc);
if (IS_ERR(state->dpu_plane_states))
kfree(state);
}
}
static struct drm_crtc_state *
dpu_drm_crtc_duplicate_state(struct drm_crtc *crtc)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
struct dpu_crtc_state *state;
if (WARN_ON(!crtc->state))
return NULL;
state = kzalloc(sizeof(*state), GFP_KERNEL);
if (!state)
return NULL;
state->dpu_plane_states = alloc_dpu_plane_states(dpu_crtc);
if (IS_ERR(state->dpu_plane_states)) {
kfree(state);
return NULL;
}
__drm_atomic_helper_crtc_duplicate_state(crtc,
&state->imx_crtc_state.base);
return &state->imx_crtc_state.base;
}
static void dpu_drm_crtc_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(state);
struct dpu_crtc_state *dcstate;
if (state) {
__drm_atomic_helper_crtc_destroy_state(state);
dcstate = to_dpu_crtc_state(imx_crtc_state);
kfree(dcstate->dpu_plane_states);
kfree(dcstate);
}
}
static void dpu_drm_crtc_destroy(struct drm_crtc *crtc)
{
imx_drm_remove_crtc(to_dpu_crtc(crtc)->imx_crtc);
}
static const struct drm_crtc_funcs dpu_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.destroy = dpu_drm_crtc_destroy,
.page_flip = drm_atomic_helper_page_flip,
.reset = dpu_drm_crtc_reset,
.atomic_duplicate_state = dpu_drm_crtc_duplicate_state,
.atomic_destroy_state = dpu_drm_crtc_destroy_state,
};
static irqreturn_t dpu_vbl_irq_handler(int irq, void *dev_id)
{
struct dpu_crtc *dpu_crtc = dev_id;
drm_crtc_handle_vblank(&dpu_crtc->base);
return IRQ_HANDLED;
}
static irqreturn_t dpu_safety_shdld_irq_handler(int irq, void *dev_id)
{
struct dpu_crtc *dpu_crtc = dev_id;
complete(&dpu_crtc->safety_shdld_done);
return IRQ_HANDLED;
}
static irqreturn_t dpu_content_shdld_irq_handler(int irq, void *dev_id)
{
struct dpu_crtc *dpu_crtc = dev_id;
complete(&dpu_crtc->content_shdld_done);
return IRQ_HANDLED;
}
static irqreturn_t dpu_dec_shdld_irq_handler(int irq, void *dev_id)
{
struct dpu_crtc *dpu_crtc = dev_id;
complete(&dpu_crtc->dec_shdld_done);
return IRQ_HANDLED;
}
static int dpu_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *crtc_state)
{
struct drm_plane *plane;
struct drm_plane_state *plane_state;
struct dpu_plane_state *dpstate;
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc_state);
struct dpu_crtc_state *dcstate = to_dpu_crtc_state(imx_crtc_state);
int i = 0;
/*
* cache the plane states so that the planes can be disabled in
* ->atomic_begin.
*/
drm_for_each_plane_mask(plane, crtc->dev, crtc_state->plane_mask) {
plane_state =
drm_atomic_get_plane_state(crtc_state->state, plane);
if (IS_ERR(plane_state))
return PTR_ERR(plane_state);
dpstate = to_dpu_plane_state(plane_state);
dcstate->dpu_plane_states[i++] = dpstate;
}
return 0;
}
static void dpu_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
struct imx_crtc_state *imx_crtc_state =
to_imx_crtc_state(old_crtc_state);
struct dpu_crtc_state *old_dcstate = to_dpu_crtc_state(imx_crtc_state);
int i;
/*
* Disable all planes' resources in SHADOW only.
* Whether any of them would be disabled or kept running depends
* on new plane states' commit.
*/
for (i = 0; i < dpu_crtc->hw_plane_num; i++) {
struct dpu_plane_state *old_dpstate;
struct drm_plane_state *plane_state;
struct dpu_plane *dplane;
struct drm_plane *plane;
struct dpu_plane_res *res;
struct dpu_fetchdecode *fd;
struct dpu_fetcheco *fe;
struct dpu_hscaler *hs;
struct dpu_vscaler *vs;
struct dpu_layerblend *lb;
struct dpu_extdst *ed;
extdst_src_sel_t ed_src;
int fd_id, lb_id;
bool crtc_disabling_on_primary = false;
old_dpstate = old_dcstate->dpu_plane_states[i];
if (!old_dpstate)
continue;
plane_state = &old_dpstate->base;
dplane = to_dpu_plane(plane_state->plane);
res = &dplane->grp->res;
fd_id = source_to_id(old_dpstate->source);
if (fd_id < 0)
return;
lb_id = blend_to_id(old_dpstate->blend);
if (lb_id < 0)
return;
fd = res->fd[fd_id];
lb = res->lb[lb_id];
fe = fetchdecode_get_fetcheco(fd);
hs = fetchdecode_get_hscaler(fd);
vs = fetchdecode_get_vscaler(fd);
layerblend_pixengcfg_clken(lb, CLKEN__DISABLE);
hscaler_pixengcfg_clken(hs, CLKEN__DISABLE);
vscaler_pixengcfg_clken(vs, CLKEN__DISABLE);
hscaler_mode(hs, SCALER_NEUTRAL);
vscaler_mode(vs, SCALER_NEUTRAL);
if (old_dpstate->is_top) {
ed = res->ed[dplane->stream_id];
ed_src = dplane->stream_id ?
ED_SRC_CONSTFRAME1 : ED_SRC_CONSTFRAME0;
extdst_pixengcfg_src_sel(ed, ed_src);
}
plane = old_dpstate->base.plane;
if (!crtc->state->enable &&
plane->type == DRM_PLANE_TYPE_PRIMARY)
crtc_disabling_on_primary = true;
if (crtc_disabling_on_primary && old_dpstate->use_prefetch) {
fetchdecode_pin_off(fd);
if (fetcheco_is_enabled(fe))
fetcheco_pin_off(fe);
} else {
fetchdecode_source_buffer_disable(fd);
fetchdecode_pixengcfg_dynamic_src_sel(fd,
FD_SRC_DISABLE);
fetcheco_source_buffer_disable(fe);
fetchdecode_unpin_off(fd);
fetcheco_unpin_off(fe);
}
}
}
static void dpu_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
struct imx_crtc_state *imx_crtc_state =
to_imx_crtc_state(old_crtc_state);
struct dpu_crtc_state *old_dcstate = to_dpu_crtc_state(imx_crtc_state);
struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
struct dpu_plane_res *res = &dplane->grp->res;
struct dpu_extdst *ed = res->ed[dplane->stream_id];
unsigned long ret;
int i;
bool need_modeset = drm_atomic_crtc_needs_modeset(crtc->state);
if (!crtc->state->active && !old_crtc_state->active)
return;
if (!need_modeset) {
enable_irq(dpu_crtc->content_shdld_irq);
extdst_pixengcfg_sync_trigger(ed);
ret = wait_for_completion_timeout(&dpu_crtc->content_shdld_done,
HZ);
if (ret == 0)
dev_warn(dpu_crtc->dev,
"flush - wait for content shdld done timeout\n");
disable_irq(dpu_crtc->content_shdld_irq);
WARN_ON(!crtc->state->event);
if (crtc->state->event) {
spin_lock_irq(&crtc->dev->event_lock);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
} else if (!crtc->state->active) {
extdst_pixengcfg_sync_trigger(ed);
}
for (i = 0; i < dpu_crtc->hw_plane_num; i++) {
struct dpu_plane_state *old_dpstate;
struct drm_plane_state *plane_state;
struct dpu_plane *dplane;
struct dpu_plane_res *res;
struct dpu_fetchdecode *fd;
struct dpu_fetcheco *fe;
struct dpu_hscaler *hs;
struct dpu_vscaler *vs;
int fd_id;
old_dpstate = old_dcstate->dpu_plane_states[i];
if (!old_dpstate)
continue;
plane_state = &old_dpstate->base;
dplane = to_dpu_plane(plane_state->plane);
res = &dplane->grp->res;
fd_id = source_to_id(old_dpstate->source);
if (fd_id < 0)
return;
fd = res->fd[fd_id];
if (!fetchdecode_is_enabled(fd) ||
fetchdecode_is_pinned_off(fd))
fetchdecode_set_stream_id(fd, DPU_PLANE_SRC_DISABLED);
fe = fetchdecode_get_fetcheco(fd);
if (!fetcheco_is_enabled(fe) || fetcheco_is_pinned_off(fe))
fetcheco_set_stream_id(fe, DPU_PLANE_SRC_DISABLED);
hs = fetchdecode_get_hscaler(fd);
if (!hscaler_is_enabled(hs))
hscaler_set_stream_id(hs, DPU_PLANE_SRC_DISABLED);
vs = fetchdecode_get_vscaler(fd);
if (!vscaler_is_enabled(vs))
vscaler_set_stream_id(vs, DPU_PLANE_SRC_DISABLED);
}
}
static void dpu_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state);
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
struct dpu_plane *dplane = to_dpu_plane(crtc->primary);
struct dpu_plane_res *res = &dplane->grp->res;
struct dpu_extdst *plane_ed = res->ed[dplane->stream_id];
extdst_src_sel_t ed_src;
dev_dbg(dpu_crtc->dev, "%s: mode->hdisplay: %d\n", __func__,
mode->hdisplay);
dev_dbg(dpu_crtc->dev, "%s: mode->vdisplay: %d\n", __func__,
mode->vdisplay);
framegen_cfg_videomode(dpu_crtc->fg, mode);
framegen_displaymode(dpu_crtc->fg, FGDM__SEC_ON_TOP);
framegen_panic_displaymode(dpu_crtc->fg, FGDM__TEST);
tcon_cfg_videomode(dpu_crtc->tcon, mode);
tcon_set_fmt(dpu_crtc->tcon, imx_crtc_state->bus_format);
disengcfg_polarity_ctrl(dpu_crtc->dec, mode->flags);
constframe_framedimensions(dpu_crtc->cf,
mode->crtc_hdisplay,
mode->crtc_vdisplay);
ed_src = dpu_crtc->stream_id ? ED_SRC_CONSTFRAME5 : ED_SRC_CONSTFRAME4;
extdst_pixengcfg_src_sel(dpu_crtc->ed, ed_src);
ed_src = dpu_crtc->stream_id ? ED_SRC_CONSTFRAME1 : ED_SRC_CONSTFRAME0;
extdst_pixengcfg_src_sel(plane_ed, ed_src);
}
static const struct drm_crtc_helper_funcs dpu_helper_funcs = {
.mode_set_nofb = dpu_crtc_mode_set_nofb,
.atomic_check = dpu_crtc_atomic_check,
.atomic_begin = dpu_crtc_atomic_begin,
.atomic_flush = dpu_crtc_atomic_flush,
.enable = dpu_crtc_enable,
.atomic_disable = dpu_crtc_atomic_disable,
};
static int dpu_enable_vblank(struct drm_crtc *crtc)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
enable_irq(dpu_crtc->vbl_irq);
return 0;
}
static void dpu_disable_vblank(struct drm_crtc *crtc)
{
struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
disable_irq_nosync(dpu_crtc->vbl_irq);
}
static const struct imx_drm_crtc_helper_funcs dpu_crtc_helper_funcs = {
.enable_vblank = dpu_enable_vblank,
.disable_vblank = dpu_disable_vblank,
.crtc_funcs = &dpu_crtc_funcs,
.crtc_helper_funcs = &dpu_helper_funcs,
};
static void dpu_crtc_put_resources(struct dpu_crtc *dpu_crtc)
{
if (!IS_ERR_OR_NULL(dpu_crtc->cf))
dpu_cf_put(dpu_crtc->cf);
if (!IS_ERR_OR_NULL(dpu_crtc->dec))
dpu_dec_put(dpu_crtc->dec);
if (!IS_ERR_OR_NULL(dpu_crtc->ed))
dpu_ed_put(dpu_crtc->ed);
if (!IS_ERR_OR_NULL(dpu_crtc->fg))
dpu_fg_put(dpu_crtc->fg);
if (!IS_ERR_OR_NULL(dpu_crtc->tcon))
dpu_tcon_put(dpu_crtc->tcon);
}
static int dpu_crtc_get_resources(struct dpu_crtc *dpu_crtc,
unsigned int stream_id)
{
struct dpu_soc *dpu = dev_get_drvdata(dpu_crtc->dev->parent);
int ret;
dpu_crtc->cf = dpu_cf_get(dpu, stream_id + 4);
if (IS_ERR(dpu_crtc->cf)) {
ret = PTR_ERR(dpu_crtc->cf);
goto err_out;
}
dpu_crtc->dec = dpu_dec_get(dpu, stream_id);
if (IS_ERR(dpu_crtc->dec)) {
ret = PTR_ERR(dpu_crtc->dec);
goto err_out;
}
dpu_crtc->ed = dpu_ed_get(dpu, stream_id + 4);
if (IS_ERR(dpu_crtc->ed)) {
ret = PTR_ERR(dpu_crtc->ed);
goto err_out;
}
dpu_crtc->fg = dpu_fg_get(dpu, stream_id);
if (IS_ERR(dpu_crtc->fg)) {
ret = PTR_ERR(dpu_crtc->fg);
goto err_out;
}
dpu_crtc->tcon = dpu_tcon_get(dpu, stream_id);
if (IS_ERR(dpu_crtc->tcon)) {
ret = PTR_ERR(dpu_crtc->tcon);
goto err_out;
}
return 0;
err_out:
dpu_crtc_put_resources(dpu_crtc);
return ret;
}
static int dpu_crtc_init(struct dpu_crtc *dpu_crtc,
struct dpu_client_platformdata *pdata, struct drm_device *drm)
{
struct dpu_soc *dpu = dev_get_drvdata(dpu_crtc->dev->parent);
struct device *dev = dpu_crtc->dev;
struct dpu_plane_grp *plane_grp = pdata->plane_grp;
unsigned int stream_id = pdata->stream_id;
int i, ret;
init_completion(&dpu_crtc->safety_shdld_done);
init_completion(&dpu_crtc->content_shdld_done);
init_completion(&dpu_crtc->dec_shdld_done);
dpu_crtc->stream_id = stream_id;
dpu_crtc->hw_plane_num = plane_grp->hw_plane_num;
dpu_crtc->plane = devm_kcalloc(dev, dpu_crtc->hw_plane_num,
sizeof(*dpu_crtc->plane), GFP_KERNEL);
if (!dpu_crtc->plane)
return -ENOMEM;
ret = dpu_crtc_get_resources(dpu_crtc, stream_id);
if (ret) {
dev_err(dev, "getting resources failed with %d.\n", ret);
return ret;
}
plane_grp->res.fg[stream_id] = dpu_crtc->fg;
dpu_crtc->plane[0] = dpu_plane_init(drm, 0, stream_id, plane_grp,
DRM_PLANE_TYPE_PRIMARY);
if (IS_ERR(dpu_crtc->plane[0])) {
ret = PTR_ERR(dpu_crtc->plane[0]);
dev_err(dev, "initializing plane0 failed with %d.\n", ret);
goto err_put_resources;
}
ret = imx_drm_add_crtc(drm, &dpu_crtc->base, &dpu_crtc->imx_crtc,
&dpu_crtc->plane[0]->base, &dpu_crtc_helper_funcs,
pdata->of_node);
if (ret) {
dev_err(dev, "adding crtc failed with %d.\n", ret);
goto err_put_resources;
}
for (i = 1; i < dpu_crtc->hw_plane_num; i++) {
dpu_crtc->plane[i] = dpu_plane_init(drm,
drm_crtc_mask(&dpu_crtc->base),
stream_id, plane_grp,
DRM_PLANE_TYPE_OVERLAY);
if (IS_ERR(dpu_crtc->plane[i])) {
ret = PTR_ERR(dpu_crtc->plane[i]);
dev_err(dev, "initializing plane%d failed with %d.\n",
i, ret);
goto err_remove_crtc;
}
}
dpu_crtc->vbl_irq = dpu_map_inner_irq(dpu, stream_id ?
IRQ_DISENGCFG_FRAMECOMPLETE1 :
IRQ_DISENGCFG_FRAMECOMPLETE0);
ret = devm_request_irq(dev, dpu_crtc->vbl_irq, dpu_vbl_irq_handler, 0,
"imx_drm", dpu_crtc);
if (ret < 0) {
dev_err(dev, "vblank irq request failed with %d.\n", ret);
goto err_remove_crtc;
}
disable_irq(dpu_crtc->vbl_irq);
dpu_crtc->safety_shdld_irq = dpu_map_inner_irq(dpu, stream_id ?
IRQ_EXTDST5_SHDLOAD : IRQ_EXTDST4_SHDLOAD);
ret = devm_request_irq(dev, dpu_crtc->safety_shdld_irq,
dpu_safety_shdld_irq_handler, 0, "imx_drm",
dpu_crtc);
if (ret < 0) {
dev_err(dev,
"safety shadow load irq request failed with %d.\n",
ret);
goto err_remove_crtc;
}
disable_irq(dpu_crtc->safety_shdld_irq);
dpu_crtc->content_shdld_irq = dpu_map_inner_irq(dpu, stream_id ?
IRQ_EXTDST1_SHDLOAD : IRQ_EXTDST0_SHDLOAD);
ret = devm_request_irq(dev, dpu_crtc->content_shdld_irq,
dpu_content_shdld_irq_handler, 0, "imx_drm",
dpu_crtc);
if (ret < 0) {
dev_err(dev,
"content shadow load irq request failed with %d.\n",
ret);
goto err_remove_crtc;
}
disable_irq(dpu_crtc->content_shdld_irq);
dpu_crtc->dec_shdld_irq = dpu_map_inner_irq(dpu, stream_id ?
IRQ_DISENGCFG_SHDLOAD1 : IRQ_DISENGCFG_SHDLOAD0);
ret = devm_request_irq(dev, dpu_crtc->dec_shdld_irq,
dpu_dec_shdld_irq_handler, 0, "imx_drm",
dpu_crtc);
if (ret < 0) {
dev_err(dev,
"DEC shadow load irq request failed with %d.\n",
ret);
goto err_remove_crtc;
}
disable_irq(dpu_crtc->dec_shdld_irq);
return 0;
err_remove_crtc:
imx_drm_remove_crtc(dpu_crtc->imx_crtc);
err_put_resources:
dpu_crtc_put_resources(dpu_crtc);
return ret;
}
static int dpu_crtc_bind(struct device *dev, struct device *master, void *data)
{
struct dpu_client_platformdata *pdata = dev->platform_data;
struct drm_device *drm = data;
struct dpu_crtc *dpu_crtc;
int ret;
dpu_crtc = devm_kzalloc(dev, sizeof(*dpu_crtc), GFP_KERNEL);
if (!dpu_crtc)
return -ENOMEM;
dpu_crtc->dev = dev;
ret = dpu_crtc_init(dpu_crtc, pdata, drm);
if (ret)
return ret;
if (!drm->mode_config.funcs)
drm->mode_config.funcs = &dpu_drm_mode_config_funcs;
dev_set_drvdata(dev, dpu_crtc);
return 0;
}
static void dpu_crtc_unbind(struct device *dev, struct device *master,
void *data)
{
struct dpu_crtc *dpu_crtc = dev_get_drvdata(dev);
dpu_crtc_put_resources(dpu_crtc);
}
static const struct component_ops dpu_crtc_ops = {
.bind = dpu_crtc_bind,
.unbind = dpu_crtc_unbind,
};
static int dpu_crtc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
if (!dev->platform_data)
return -EINVAL;
return component_add(dev, &dpu_crtc_ops);
}
static int dpu_crtc_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &dpu_crtc_ops);
return 0;
}
static struct platform_driver dpu_crtc_driver = {
.driver = {
.name = "imx-dpu-crtc",
},
.probe = dpu_crtc_probe,
.remove = dpu_crtc_remove,
};
module_platform_driver(dpu_crtc_driver);
MODULE_AUTHOR("NXP Semiconductor");
MODULE_DESCRIPTION("i.MX DPU CRTC");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:imx-dpu-crtc");