blob: 9c7642e4a9974669b910c6b0e25eef6b6d3e08e4 [file] [log] [blame]
/*
* Copyright 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 <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-lcdif.h>
#include <video/videomode.h>
#include "imx-drm.h"
#include "lcdif-plane.h"
#include "lcdif-kms.h"
struct lcdif_crtc {
struct device *dev;
struct drm_crtc base;
struct lcdif_plane *plane[2];
int vbl_irq;
u32 pix_fmt; /* drm fourcc */
};
#define to_lcdif_crtc(crtc) container_of(crtc, struct lcdif_crtc, base)
static void lcdif_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 *lcdif_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 lcdif_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 lcdif_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct lcdif_crtc *lcdif_crtc = to_lcdif_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(lcdif_crtc->dev,
"unsupported bus format: %#x\n",
imx_crtc_state->bus_format);
return -EINVAL;
}
return 0;
}
static void lcdif_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 lcdif_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
/* LCDIF doesn't have command buffer */
return;
}
static void lcdif_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_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(lcdif_crtc->dev->parent);
lcdif_set_mode(lcdif, &vm);
/* config LCDIF output bus format */
lcdif_set_bus_fmt(lcdif, imx_crtc_state->bus_format);
/* defer the lcdif controller enable to plane update,
* since until then the lcdif config is complete to
* enable the controller to run actually.
*/
}
static void lcdif_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_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);
lcdif_disable_controller(lcdif);
pm_runtime_put(lcdif_crtc->dev->parent);
}
static const struct drm_crtc_helper_funcs lcdif_helper_funcs = {
.atomic_check = lcdif_crtc_atomic_check,
.atomic_begin = lcdif_crtc_atomic_begin,
.atomic_flush = lcdif_crtc_atomic_flush,
.atomic_enable = lcdif_crtc_atomic_enable,
.atomic_disable = lcdif_crtc_atomic_disable,
};
static int lcdif_enable_vblank(struct drm_crtc *crtc)
{
struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
lcdif_vblank_irq_enable(lcdif);
enable_irq(lcdif_crtc->vbl_irq);
return 0;
}
static void lcdif_disable_vblank(struct drm_crtc *crtc)
{
struct lcdif_crtc *lcdif_crtc = to_lcdif_crtc(crtc);
struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
disable_irq_nosync(lcdif_crtc->vbl_irq);
lcdif_vblank_irq_disable(lcdif);
}
static const struct drm_crtc_funcs lcdif_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.destroy = drm_crtc_cleanup,
.page_flip = drm_atomic_helper_page_flip,
.reset = lcdif_crtc_reset,
.atomic_duplicate_state = lcdif_crtc_duplicate_state,
.atomic_destroy_state = lcdif_crtc_destroy_state,
.enable_vblank = lcdif_enable_vblank,
.disable_vblank = lcdif_disable_vblank,
};
static irqreturn_t lcdif_crtc_vblank_irq_handler(int irq, void *dev_id)
{
struct lcdif_crtc *lcdif_crtc = dev_id;
struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
drm_crtc_handle_vblank(&lcdif_crtc->base);
lcdif_vblank_irq_clear(lcdif);
return IRQ_HANDLED;
}
static int lcdif_crtc_init(struct lcdif_crtc *lcdif_crtc,
struct lcdif_client_platformdata *pdata,
struct drm_device *drm)
{
int ret;
struct lcdif_plane *primary = lcdif_crtc->plane[0];
struct lcdif_soc *lcdif = dev_get_drvdata(lcdif_crtc->dev->parent);
/* Primary plane
* The 'possible_crtcs' of primary plane will be
* recalculated during the 'crtc' initialization
* later.
*/
primary = lcdif_plane_init(drm, lcdif, 0, DRM_PLANE_TYPE_PRIMARY, 0);
if (IS_ERR(primary))
return PTR_ERR(primary);
lcdif_crtc->plane[0] = primary;
/* TODO: Overlay plane */
lcdif_crtc->base.port = pdata->of_node;
drm_crtc_helper_add(&lcdif_crtc->base, &lcdif_helper_funcs);
ret = drm_crtc_init_with_planes(drm, &lcdif_crtc->base,
&lcdif_crtc->plane[0]->base, NULL,
&lcdif_crtc_funcs, NULL);
if (ret) {
dev_err(lcdif_crtc->dev, "failed to init crtc\n");
goto primary_plane_deinit;
}
lcdif_crtc->vbl_irq = lcdif_vblank_irq_get(lcdif);
WARN_ON(lcdif_crtc->vbl_irq < 0);
ret = devm_request_irq(lcdif_crtc->dev, lcdif_crtc->vbl_irq,
lcdif_crtc_vblank_irq_handler, 0,
dev_name(lcdif_crtc->dev), lcdif_crtc);
if (ret) {
dev_err(lcdif_crtc->dev,
"vblank irq request failed: %d\n", ret);
goto primary_plane_deinit;
}
disable_irq(lcdif_crtc->vbl_irq);
return 0;
primary_plane_deinit:
lcdif_plane_deinit(drm, primary);
return ret;
}
static int lcdif_crtc_bind(struct device *dev, struct device *master,
void *data)
{
int ret;
struct drm_device *drm = data;
struct lcdif_crtc *lcdif_crtc;
struct lcdif_client_platformdata *pdata = dev->platform_data;
dev_dbg(dev, "%s: lcdif crtc bind begin\n", __func__);
lcdif_crtc = devm_kzalloc(dev, sizeof(*lcdif_crtc), GFP_KERNEL);
if (!lcdif_crtc)
return -ENOMEM;
lcdif_crtc->dev = dev;
ret = lcdif_crtc_init(lcdif_crtc, pdata, drm);
if (ret)
return ret;
if (!drm->mode_config.funcs)
drm->mode_config.funcs = &lcdif_drm_mode_config_funcs;
if (!drm->mode_config.helper_private)
drm->mode_config.helper_private = &lcdif_drm_mode_config_helpers;
/* limit the max width and height */
drm->mode_config.max_width = 1920;
drm->mode_config.max_height = 1920;
dev_set_drvdata(dev, lcdif_crtc);
dev_dbg(dev, "%s: lcdif crtc bind end\n", __func__);
return 0;
}
static void lcdif_crtc_unbind(struct device *dev, struct device *master,
void *data)
{
struct drm_device *drm = data;
struct lcdif_crtc *lcdif_crtc = dev_get_drvdata(dev);
lcdif_plane_deinit(drm, lcdif_crtc->plane[0]);
}
static const struct component_ops lcdif_crtc_ops = {
.bind = lcdif_crtc_bind,
.unbind = lcdif_crtc_unbind,
};
static int lcdif_crtc_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
dev_dbg(&pdev->dev, "%s: lcdif crtc probe begin\n", __func__);
if (!dev->platform_data) {
dev_err(dev, "no platform data\n");
return -EINVAL;
}
return component_add(dev, &lcdif_crtc_ops);
}
static int lcdif_crtc_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &lcdif_crtc_ops);
return 0;
}
static struct platform_driver lcdif_crtc_driver = {
.probe = lcdif_crtc_probe,
.remove = lcdif_crtc_remove,
.driver = {
.name = "imx-lcdif-crtc",
},
};
module_platform_driver(lcdif_crtc_driver);
MODULE_DESCRIPTION("NXP i.MX LCDIF DRM CRTC driver");
MODULE_AUTHOR("Fancy Fang <chen.fang@nxp.com>");
MODULE_LICENSE("GPL");