374 lines
9.6 KiB
C
374 lines
9.6 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Copyright 2019,2020 NXP
|
|
*/
|
|
|
|
#include <linux/component.h>
|
|
#include <linux/device.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <video/imx-lcdifv3.h>
|
|
#include <video/videomode.h>
|
|
|
|
#include "imx-drm.h"
|
|
#include "lcdifv3-plane.h"
|
|
#include "lcdifv3-kms.h"
|
|
|
|
struct lcdifv3_crtc {
|
|
struct device *dev;
|
|
|
|
struct drm_crtc base;
|
|
struct lcdifv3_plane *plane[2];
|
|
|
|
int vbl_irq;
|
|
u32 pix_fmt; /* drm fourcc */
|
|
};
|
|
|
|
#define to_lcdifv3_crtc(crtc) container_of(crtc, struct lcdifv3_crtc, base)
|
|
|
|
static void lcdifv3_crtc_reset(struct drm_crtc *crtc)
|
|
{
|
|
struct imx_crtc_state *state;
|
|
|
|
if (crtc->state) {
|
|
__drm_atomic_helper_crtc_destroy_state(crtc->state);
|
|
|
|
state = to_imx_crtc_state(crtc->state);
|
|
kfree(state);
|
|
crtc->state = NULL;
|
|
}
|
|
|
|
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
|
if (!state)
|
|
return;
|
|
|
|
crtc->state = &state->base;
|
|
crtc->state->crtc = crtc;
|
|
}
|
|
|
|
static struct drm_crtc_state *lcdifv3_crtc_duplicate_state(struct drm_crtc *crtc)
|
|
{
|
|
struct imx_crtc_state *state, *orig_state;
|
|
|
|
if (WARN_ON(!crtc->state))
|
|
return NULL;
|
|
|
|
state = kzalloc(sizeof(*state), GFP_KERNEL);
|
|
if (!state)
|
|
return NULL;
|
|
|
|
__drm_atomic_helper_crtc_duplicate_state(crtc, &state->base);
|
|
|
|
orig_state = to_imx_crtc_state(crtc->state);
|
|
state->bus_format = orig_state->bus_format;
|
|
state->bus_flags = orig_state->bus_flags;
|
|
state->di_hsync_pin = orig_state->di_hsync_pin;
|
|
state->di_vsync_pin = orig_state->di_vsync_pin;
|
|
|
|
return &state->base;
|
|
}
|
|
|
|
static void lcdifv3_crtc_destroy_state(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *state)
|
|
{
|
|
__drm_atomic_helper_crtc_destroy_state(state);
|
|
kfree(to_imx_crtc_state(state));
|
|
}
|
|
|
|
static int lcdifv3_crtc_atomic_check(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *state)
|
|
{
|
|
struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
|
|
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(state);
|
|
|
|
/* Don't check 'bus_format' when CRTC is
|
|
* going to be disabled.
|
|
*/
|
|
if (!state->enable)
|
|
return 0;
|
|
|
|
/* For the commit that the CRTC is active
|
|
* without planes attached to it should be
|
|
* invalid.
|
|
*/
|
|
if (state->active && !state->plane_mask)
|
|
return -EINVAL;
|
|
|
|
/* check the requested bus format can be
|
|
* supported by LCDIF CTRC or not
|
|
*/
|
|
switch (imx_crtc_state->bus_format) {
|
|
case MEDIA_BUS_FMT_RGB565_1X16:
|
|
case MEDIA_BUS_FMT_RGB666_1X18:
|
|
case MEDIA_BUS_FMT_RGB888_1X24:
|
|
break;
|
|
default:
|
|
dev_err(lcdifv3_crtc->dev,
|
|
"unsupported bus format: %#x\n",
|
|
imx_crtc_state->bus_format);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lcdifv3_crtc_atomic_begin(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *old_crtc_state)
|
|
{
|
|
drm_crtc_vblank_on(crtc);
|
|
|
|
spin_lock_irq(&crtc->dev->event_lock);
|
|
if (crtc->state->event) {
|
|
WARN_ON(drm_crtc_vblank_get(crtc));
|
|
drm_crtc_arm_vblank_event(crtc, crtc->state->event);
|
|
crtc->state->event = NULL;
|
|
}
|
|
spin_unlock_irq(&crtc->dev->event_lock);
|
|
}
|
|
|
|
static void lcdifv3_crtc_atomic_flush(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *old_crtc_state)
|
|
{
|
|
struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
|
|
struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
|
|
|
|
/* kick shadow load for plane config */
|
|
lcdifv3_en_shadow_load(lcdifv3);
|
|
}
|
|
|
|
static void lcdifv3_crtc_atomic_enable(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *old_crtc_state)
|
|
{
|
|
struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
|
|
struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
|
|
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
|
|
struct imx_crtc_state *imx_crtc_state = to_imx_crtc_state(crtc->state);
|
|
struct videomode vm;
|
|
|
|
drm_display_mode_to_videomode(mode, &vm);
|
|
|
|
if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_DE_HIGH)
|
|
vm.flags |= DISPLAY_FLAGS_DE_HIGH;
|
|
else
|
|
vm.flags |= DISPLAY_FLAGS_DE_LOW;
|
|
|
|
if (imx_crtc_state->bus_flags & DRM_BUS_FLAG_PIXDATA_POSEDGE)
|
|
vm.flags |= DISPLAY_FLAGS_PIXDATA_POSEDGE;
|
|
else
|
|
vm.flags |= DISPLAY_FLAGS_PIXDATA_NEGEDGE;
|
|
|
|
pm_runtime_get_sync(lcdifv3_crtc->dev->parent);
|
|
|
|
lcdifv3_set_mode(lcdifv3, &vm);
|
|
|
|
/* config LCDIF output bus format */
|
|
lcdifv3_set_bus_fmt(lcdifv3, imx_crtc_state->bus_format);
|
|
|
|
/* run LCDIFv3 */
|
|
lcdifv3_enable_controller(lcdifv3);
|
|
}
|
|
|
|
static void lcdifv3_crtc_atomic_disable(struct drm_crtc *crtc,
|
|
struct drm_crtc_state *old_crtc_state)
|
|
{
|
|
struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
|
|
struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
|
|
|
|
spin_lock_irq(&crtc->dev->event_lock);
|
|
if (crtc->state->event) {
|
|
drm_crtc_send_vblank_event(crtc, crtc->state->event);
|
|
crtc->state->event = NULL;
|
|
}
|
|
spin_unlock_irq(&crtc->dev->event_lock);
|
|
|
|
drm_crtc_vblank_off(crtc);
|
|
|
|
lcdifv3_disable_controller(lcdifv3);
|
|
|
|
pm_runtime_put(lcdifv3_crtc->dev->parent);
|
|
}
|
|
|
|
static const struct drm_crtc_helper_funcs lcdifv3_helper_funcs = {
|
|
.atomic_check = lcdifv3_crtc_atomic_check,
|
|
.atomic_begin = lcdifv3_crtc_atomic_begin,
|
|
.atomic_flush = lcdifv3_crtc_atomic_flush,
|
|
.atomic_enable = lcdifv3_crtc_atomic_enable,
|
|
.atomic_disable = lcdifv3_crtc_atomic_disable,
|
|
};
|
|
|
|
static int lcdifv3_enable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
|
|
struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
|
|
|
|
lcdifv3_vblank_irq_enable(lcdifv3);
|
|
enable_irq(lcdifv3_crtc->vbl_irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lcdifv3_disable_vblank(struct drm_crtc *crtc)
|
|
{
|
|
struct lcdifv3_crtc *lcdifv3_crtc = to_lcdifv3_crtc(crtc);
|
|
struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
|
|
|
|
disable_irq_nosync(lcdifv3_crtc->vbl_irq);
|
|
lcdifv3_vblank_irq_disable(lcdifv3);
|
|
}
|
|
|
|
static const struct drm_crtc_funcs lcdifv3_crtc_funcs = {
|
|
.set_config = drm_atomic_helper_set_config,
|
|
.destroy = drm_crtc_cleanup,
|
|
.page_flip = drm_atomic_helper_page_flip,
|
|
.reset = lcdifv3_crtc_reset,
|
|
.atomic_duplicate_state = lcdifv3_crtc_duplicate_state,
|
|
.atomic_destroy_state = lcdifv3_crtc_destroy_state,
|
|
.enable_vblank = lcdifv3_enable_vblank,
|
|
.disable_vblank = lcdifv3_disable_vblank,
|
|
};
|
|
|
|
static irqreturn_t lcdifv3_crtc_vblank_irq_handler(int irq, void *dev_id)
|
|
{
|
|
struct lcdifv3_crtc *lcdifv3_crtc = dev_id;
|
|
struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
|
|
|
|
drm_crtc_handle_vblank(&lcdifv3_crtc->base);
|
|
|
|
lcdifv3_vblank_irq_clear(lcdifv3);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int lcdifv3_crtc_init(struct lcdifv3_crtc *lcdifv3_crtc,
|
|
struct lcdifv3_client_platformdata *pdata,
|
|
struct drm_device *drm)
|
|
{
|
|
int ret;
|
|
struct lcdifv3_plane *primary = lcdifv3_crtc->plane[0];
|
|
struct lcdifv3_soc *lcdifv3 = dev_get_drvdata(lcdifv3_crtc->dev->parent);
|
|
|
|
/* Primary plane
|
|
* The 'possible_crtcs' of primary plane will be
|
|
* recalculated during the 'crtc' initialization
|
|
* later.
|
|
*/
|
|
primary = lcdifv3_plane_init(drm, lcdifv3, 0, DRM_PLANE_TYPE_PRIMARY, 0);
|
|
if (IS_ERR(primary))
|
|
return PTR_ERR(primary);
|
|
lcdifv3_crtc->plane[0] = primary;
|
|
|
|
/* TODO: Overlay plane */
|
|
|
|
lcdifv3_crtc->base.port = pdata->of_node;
|
|
drm_crtc_helper_add(&lcdifv3_crtc->base, &lcdifv3_helper_funcs);
|
|
ret = drm_crtc_init_with_planes(drm, &lcdifv3_crtc->base,
|
|
&lcdifv3_crtc->plane[0]->base, NULL,
|
|
&lcdifv3_crtc_funcs, NULL);
|
|
if (ret) {
|
|
dev_err(lcdifv3_crtc->dev, "failed to init crtc\n");
|
|
return ret;
|
|
}
|
|
|
|
lcdifv3_crtc->vbl_irq = lcdifv3_vblank_irq_get(lcdifv3);
|
|
WARN_ON(lcdifv3_crtc->vbl_irq < 0);
|
|
|
|
ret = devm_request_irq(lcdifv3_crtc->dev, lcdifv3_crtc->vbl_irq,
|
|
lcdifv3_crtc_vblank_irq_handler, 0,
|
|
dev_name(lcdifv3_crtc->dev), lcdifv3_crtc);
|
|
if (ret) {
|
|
dev_err(lcdifv3_crtc->dev,
|
|
"vblank irq request failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
disable_irq(lcdifv3_crtc->vbl_irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lcdifv3_crtc_bind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
int ret;
|
|
struct drm_device *drm = data;
|
|
struct lcdifv3_crtc *lcdifv3_crtc = dev_get_drvdata(dev);
|
|
struct lcdifv3_client_platformdata *pdata = dev->platform_data;
|
|
|
|
dev_dbg(dev, "%s: lcdifv3 crtc bind begin\n", __func__);
|
|
|
|
ret = lcdifv3_crtc_init(lcdifv3_crtc, pdata, drm);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (!drm->mode_config.funcs)
|
|
drm->mode_config.funcs = &lcdifv3_drm_mode_config_funcs;
|
|
|
|
if (!drm->mode_config.helper_private)
|
|
drm->mode_config.helper_private = &lcdifv3_drm_mode_config_helpers;
|
|
|
|
/* limit the max width and height */
|
|
drm->mode_config.max_width = 4096;
|
|
drm->mode_config.max_height = 4096;
|
|
|
|
dev_dbg(dev, "%s: lcdifv3 crtc bind end\n", __func__);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lcdifv3_crtc_unbind(struct device *dev, struct device *master,
|
|
void *data)
|
|
{
|
|
/* No special to be done */
|
|
}
|
|
|
|
static const struct component_ops lcdifv3_crtc_ops = {
|
|
.bind = lcdifv3_crtc_bind,
|
|
.unbind = lcdifv3_crtc_unbind,
|
|
};
|
|
|
|
static int lcdifv3_crtc_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct lcdifv3_crtc *lcdifv3_crtc;
|
|
|
|
dev_dbg(&pdev->dev, "%s: lcdifv3 crtc probe begin\n", __func__);
|
|
|
|
if (!dev->platform_data) {
|
|
dev_err(dev, "no platform data\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
lcdifv3_crtc = devm_kzalloc(dev, sizeof(*lcdifv3_crtc), GFP_KERNEL);
|
|
if (!lcdifv3_crtc)
|
|
return -ENOMEM;
|
|
|
|
lcdifv3_crtc->dev = dev;
|
|
dev_set_drvdata(dev, lcdifv3_crtc);
|
|
|
|
return component_add(dev, &lcdifv3_crtc_ops);
|
|
}
|
|
|
|
static int lcdifv3_crtc_remove(struct platform_device *pdev)
|
|
{
|
|
component_del(&pdev->dev, &lcdifv3_crtc_ops);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver lcdifv3_crtc_driver = {
|
|
.probe = lcdifv3_crtc_probe,
|
|
.remove = lcdifv3_crtc_remove,
|
|
.driver = {
|
|
.name = "imx-lcdifv3-crtc",
|
|
},
|
|
};
|
|
module_platform_driver(lcdifv3_crtc_driver);
|
|
|
|
MODULE_DESCRIPTION("NXP i.MX LCDIFV3 DRM CRTC driver");
|
|
MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
|
|
MODULE_LICENSE("GPL");
|