Commit ec05da95 authored by Inki Dae's avatar Inki Dae

drm/exynos: updated crtc and encoder dpms framework.

With DPMS ON and OFF requests, crtc dpms would be in charge of
just only device power such as fimd or hdmi and encoder dpms
in charge of device setting(mode setting and register updating)
and also lcd panel and digital TV power.
Signed-off-by: default avatarInki Dae <inki.dae@samsung.com>
Signed-off-by: default avatarKyungmin Park <kyungmin.park@samsung.com>
parent a794d57d
......@@ -52,11 +52,13 @@
* drm framework doesn't support multiple irq yet.
* we can refer to the crtc to current hardware interrupt occured through
* this pipe value.
* @dpms: store the crtc dpms value
*/
struct exynos_drm_crtc {
struct drm_crtc drm_crtc;
struct exynos_drm_overlay overlay;
unsigned int pipe;
unsigned int dpms;
};
static void exynos_drm_crtc_apply(struct drm_crtc *crtc)
......@@ -153,26 +155,37 @@ static int exynos_drm_crtc_update(struct drm_crtc *crtc)
static void exynos_drm_crtc_dpms(struct drm_crtc *crtc, int mode)
{
struct drm_device *dev = crtc->dev;
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode);
if (exynos_crtc->dpms == mode) {
DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
return;
}
mutex_lock(&dev->struct_mutex);
switch (mode) {
case DRM_MODE_DPMS_ON:
exynos_drm_fn_encoder(crtc, &exynos_crtc->pipe,
exynos_drm_encoder_crtc_commit);
exynos_drm_fn_encoder(crtc, &mode,
exynos_drm_encoder_crtc_dpms);
exynos_crtc->dpms = mode;
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
/* TODO */
exynos_drm_fn_encoder(crtc, NULL,
exynos_drm_encoder_crtc_disable);
exynos_drm_fn_encoder(crtc, &mode,
exynos_drm_encoder_crtc_dpms);
exynos_crtc->dpms = mode;
break;
default:
DRM_DEBUG_KMS("unspecified mode %d\n", mode);
DRM_ERROR("unspecified mode %d\n", mode);
break;
}
mutex_unlock(&dev->struct_mutex);
}
static void exynos_drm_crtc_prepare(struct drm_crtc *crtc)
......@@ -188,6 +201,28 @@ static void exynos_drm_crtc_commit(struct drm_crtc *crtc)
DRM_DEBUG_KMS("%s\n", __FILE__);
/*
* when set_crtc is requested from user or at booting time,
* crtc->commit would be called without dpms call so if dpms is
* no power on then crtc->dpms should be called
* with DRM_MODE_DPMS_ON for the hardware power to be on.
*/
if (exynos_crtc->dpms != DRM_MODE_DPMS_ON) {
int mode = DRM_MODE_DPMS_ON;
/*
* enable hardware(power on) to all encoders hdmi connected
* to current crtc.
*/
exynos_drm_crtc_dpms(crtc, mode);
/*
* enable dma to all encoders connected to current crtc and
* lcd panel.
*/
exynos_drm_fn_encoder(crtc, &mode,
exynos_drm_encoder_dpms_from_crtc);
}
exynos_drm_fn_encoder(crtc, &exynos_crtc->pipe,
exynos_drm_encoder_crtc_commit);
}
......@@ -344,6 +379,7 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr)
}
exynos_crtc->pipe = nr;
exynos_crtc->dpms = DRM_MODE_DPMS_OFF;
crtc = &exynos_crtc->drm_crtc;
private->crtc[nr] = crtc;
......@@ -357,9 +393,14 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr)
int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc)
{
struct exynos_drm_private *private = dev->dev_private;
struct exynos_drm_crtc *exynos_crtc =
to_exynos_crtc(private->crtc[crtc]);
DRM_DEBUG_KMS("%s\n", __FILE__);
if (exynos_crtc->dpms != DRM_MODE_DPMS_ON)
return -EPERM;
exynos_drm_fn_encoder(private->crtc[crtc], &crtc,
exynos_drm_enable_vblank);
......@@ -369,9 +410,14 @@ int exynos_drm_crtc_enable_vblank(struct drm_device *dev, int crtc)
void exynos_drm_crtc_disable_vblank(struct drm_device *dev, int crtc)
{
struct exynos_drm_private *private = dev->dev_private;
struct exynos_drm_crtc *exynos_crtc =
to_exynos_crtc(private->crtc[crtc]);
DRM_DEBUG_KMS("%s\n", __FILE__);
if (exynos_crtc->dpms != DRM_MODE_DPMS_ON)
return;
exynos_drm_fn_encoder(private->crtc[crtc], &crtc,
exynos_drm_disable_vblank);
}
......
......@@ -144,17 +144,19 @@ struct exynos_drm_display_ops {
/*
* Exynos drm manager ops
*
* @dpms: control device power.
* @apply: set timing, vblank and overlay data to registers.
* @mode_set: convert drm_display_mode to hw specific display mode and
* would be called by encoder->mode_set().
* @commit: set current hw specific display mode to hw.
* @disable: disable hardware specific display mode.
* @enable_vblank: specific driver callback for enabling vblank interrupt.
* @disable_vblank: specific driver callback for disabling vblank interrupt.
*/
struct exynos_drm_manager_ops {
void (*dpms)(struct device *subdrv_dev, int mode);
void (*apply)(struct device *subdrv_dev);
void (*mode_set)(struct device *subdrv_dev, void *mode);
void (*commit)(struct device *subdrv_dev);
void (*disable)(struct device *subdrv_dev);
int (*enable_vblank)(struct device *subdrv_dev);
void (*disable_vblank)(struct device *subdrv_dev);
};
......
......@@ -42,49 +42,68 @@
* @drm_encoder: encoder object.
* @manager: specific encoder has its own manager to control a hardware
* appropriately and we can access a hardware drawing on this manager.
* @dpms: store the encoder dpms value.
*/
struct exynos_drm_encoder {
struct drm_encoder drm_encoder;
struct exynos_drm_manager *manager;
int dpms;
};
static void exynos_drm_encoder_dpms(struct drm_encoder *encoder, int mode)
static void exynos_drm_display_power(struct drm_encoder *encoder, int mode)
{
struct drm_device *dev = encoder->dev;
struct drm_connector *connector;
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->encoder == encoder) {
struct exynos_drm_display_ops *display_ops =
manager->display_ops;
DRM_DEBUG_KMS("connector[%d] dpms[%d]\n",
connector->base.id, mode);
if (display_ops && display_ops->power_on)
display_ops->power_on(manager->dev, mode);
}
}
}
static void exynos_drm_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct drm_device *dev = encoder->dev;
struct exynos_drm_manager *manager = exynos_drm_get_manager(encoder);
struct exynos_drm_manager_ops *manager_ops = manager->ops;
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
DRM_DEBUG_KMS("%s, encoder dpms: %d\n", __FILE__, mode);
if (exynos_encoder->dpms == mode) {
DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n");
return;
}
mutex_lock(&dev->struct_mutex);
switch (mode) {
case DRM_MODE_DPMS_ON:
if (manager_ops && manager_ops->commit)
manager_ops->commit(manager->dev);
if (manager_ops && manager_ops->apply)
manager_ops->apply(manager->dev);
exynos_drm_display_power(encoder, mode);
exynos_encoder->dpms = mode;
break;
case DRM_MODE_DPMS_STANDBY:
case DRM_MODE_DPMS_SUSPEND:
case DRM_MODE_DPMS_OFF:
/* TODO */
if (manager_ops && manager_ops->disable)
manager_ops->disable(manager->dev);
exynos_drm_display_power(encoder, mode);
exynos_encoder->dpms = mode;
break;
default:
DRM_ERROR("unspecified mode %d\n", mode);
break;
}
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->encoder == encoder) {
struct exynos_drm_display_ops *display_ops =
manager->display_ops;
DRM_DEBUG_KMS("connector[%d] dpms[%d]\n",
connector->base.id, mode);
if (display_ops && display_ops->power_on)
display_ops->power_on(manager->dev, mode);
}
}
mutex_unlock(&dev->struct_mutex);
}
static bool
......@@ -169,7 +188,6 @@ static void exynos_drm_encoder_destroy(struct drm_encoder *encoder)
exynos_encoder->manager->pipe = -1;
drm_encoder_cleanup(encoder);
encoder->dev->mode_config.num_encoder--;
kfree(exynos_encoder);
}
......@@ -199,6 +217,7 @@ exynos_drm_encoder_create(struct drm_device *dev,
return NULL;
}
exynos_encoder->dpms = DRM_MODE_DPMS_OFF;
exynos_encoder->manager = manager;
encoder = &exynos_encoder->drm_encoder;
encoder->possible_crtcs = possible_crtcs;
......@@ -294,6 +313,52 @@ void exynos_drm_encoder_crtc_commit(struct drm_encoder *encoder, void *data)
overlay_ops->commit(manager->dev);
}
void exynos_drm_encoder_dpms_from_crtc(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
int mode = *(int *)data;
DRM_DEBUG_KMS("%s\n", __FILE__);
exynos_drm_encoder_dpms(encoder, mode);
exynos_encoder->dpms = mode;
}
void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data)
{
struct drm_device *dev = encoder->dev;
struct exynos_drm_encoder *exynos_encoder = to_exynos_encoder(encoder);
struct exynos_drm_manager *manager = exynos_encoder->manager;
struct exynos_drm_manager_ops *manager_ops = manager->ops;
struct drm_connector *connector;
int mode = *(int *)data;
DRM_DEBUG_KMS("%s\n", __FILE__);
if (manager_ops && manager_ops->dpms)
manager_ops->dpms(manager->dev, mode);
/*
* set current dpms mode to the connector connected to
* current encoder. connector->dpms would be checked
* at drm_helper_connector_dpms()
*/
list_for_each_entry(connector, &dev->mode_config.connector_list, head)
if (connector->encoder == encoder)
connector->dpms = mode;
/*
* if this condition is ok then it means that the crtc is already
* detached from encoder and last function for detaching is properly
* done, so clear pipe from manager to prevent repeated call.
*/
if (mode > DRM_MODE_DPMS_ON) {
if (!encoder->crtc)
manager->pipe = -1;
}
}
void exynos_drm_encoder_crtc_mode_set(struct drm_encoder *encoder, void *data)
{
struct exynos_drm_manager *manager =
......@@ -315,14 +380,6 @@ void exynos_drm_encoder_crtc_disable(struct drm_encoder *encoder, void *data)
if (overlay_ops && overlay_ops->disable)
overlay_ops->disable(manager->dev);
/*
* crtc is already detached from encoder and last
* function for detaching is properly done, so
* clear pipe from manager to prevent repeated call
*/
if (!encoder->crtc)
manager->pipe = -1;
}
MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>");
......
......@@ -40,6 +40,9 @@ void exynos_drm_fn_encoder(struct drm_crtc *crtc, void *data,
void exynos_drm_enable_vblank(struct drm_encoder *encoder, void *data);
void exynos_drm_disable_vblank(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_crtc_commit(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_dpms_from_crtc(struct drm_encoder *encoder,
void *data);
void exynos_drm_encoder_crtc_dpms(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_crtc_mode_set(struct drm_encoder *encoder, void *data);
void exynos_drm_encoder_crtc_disable(struct drm_encoder *encoder, void *data);
......
......@@ -68,6 +68,7 @@ struct fimd_win_data {
void __iomem *vaddr;
unsigned int buf_offsize;
unsigned int line_size; /* bytes */
bool enabled;
};
struct fimd_context {
......@@ -119,7 +120,7 @@ static int fimd_display_power_on(struct device *dev, int mode)
{
DRM_DEBUG_KMS("%s\n", __FILE__);
/* TODO. */
/* TODO */
return 0;
}
......@@ -132,6 +133,31 @@ static struct exynos_drm_display_ops fimd_display_ops = {
.power_on = fimd_display_power_on,
};
static void fimd_dpms(struct device *subdrv_dev, int mode)
{
DRM_DEBUG_KMS("%s, %d\n", __FILE__, mode);
/* TODO */
}
static void fimd_apply(struct device *subdrv_dev)
{
struct fimd_context *ctx = get_fimd_context(subdrv_dev);
struct exynos_drm_manager *mgr = &ctx->subdrv.manager;
struct exynos_drm_manager_ops *mgr_ops = mgr->ops;
struct exynos_drm_overlay_ops *ovl_ops = mgr->overlay_ops;
struct fimd_win_data *win_data;
DRM_DEBUG_KMS("%s\n", __FILE__);
win_data = &ctx->win_data[ctx->default_win];
if (win_data->enabled && (ovl_ops && ovl_ops->commit))
ovl_ops->commit(subdrv_dev);
if (mgr_ops && mgr_ops->commit)
mgr_ops->commit(subdrv_dev);
}
static void fimd_commit(struct device *dev)
{
struct fimd_context *ctx = get_fimd_context(dev);
......@@ -177,40 +203,6 @@ static void fimd_commit(struct device *dev)
writel(val, ctx->regs + VIDCON0);
}
static void fimd_disable(struct device *dev)
{
struct fimd_context *ctx = get_fimd_context(dev);
struct exynos_drm_subdrv *subdrv = &ctx->subdrv;
struct drm_device *drm_dev = subdrv->drm_dev;
struct exynos_drm_manager *manager = &subdrv->manager;
u32 val;
DRM_DEBUG_KMS("%s\n", __FILE__);
/* fimd dma off */
val = readl(ctx->regs + VIDCON0);
val &= ~(VIDCON0_ENVID | VIDCON0_ENVID_F);
writel(val, ctx->regs + VIDCON0);
/*
* if vblank is enabled status with dma off then
* it disables vsync interrupt.
*/
if (drm_dev->vblank_enabled[manager->pipe] &&
atomic_read(&drm_dev->vblank_refcount[manager->pipe])) {
drm_vblank_put(drm_dev, manager->pipe);
/*
* if vblank_disable_allowed is 0 then disable
* vsync interrupt right now else the vsync interrupt
* would be disabled by drm timer once a current process
* gives up ownershop of vblank event.
*/
if (!drm_dev->vblank_disable_allowed)
drm_vblank_off(drm_dev, manager->pipe);
}
}
static int fimd_enable_vblank(struct device *dev)
{
struct fimd_context *ctx = get_fimd_context(dev);
......@@ -253,8 +245,9 @@ static void fimd_disable_vblank(struct device *dev)
}
static struct exynos_drm_manager_ops fimd_manager_ops = {
.dpms = fimd_dpms,
.apply = fimd_apply,
.commit = fimd_commit,
.disable = fimd_disable,
.enable_vblank = fimd_enable_vblank,
.disable_vblank = fimd_disable_vblank,
};
......@@ -472,16 +465,24 @@ static void fimd_win_commit(struct device *dev)
if (win != 0)
fimd_win_set_colkey(dev, win);
/* wincon */
val = readl(ctx->regs + WINCON(win));
val |= WINCONx_ENWIN;
writel(val, ctx->regs + WINCON(win));
/* Enable DMA channel and unprotect windows */
val = readl(ctx->regs + SHADOWCON);
val |= SHADOWCON_CHx_ENABLE(win);
val &= ~SHADOWCON_WINx_PROTECT(win);
writel(val, ctx->regs + SHADOWCON);
win_data->enabled = true;
}
static void fimd_win_disable(struct device *dev)
{
struct fimd_context *ctx = get_fimd_context(dev);
struct fimd_win_data *win_data;
int win = ctx->default_win;
u32 val;
......@@ -490,6 +491,8 @@ static void fimd_win_disable(struct device *dev)
if (win < 0 || win > WINDOWS_NR)
return;
win_data = &ctx->win_data[win];
/* protect windows */
val = readl(ctx->regs + SHADOWCON);
val |= SHADOWCON_WINx_PROTECT(win);
......@@ -505,6 +508,8 @@ static void fimd_win_disable(struct device *dev)
val &= ~SHADOWCON_CHx_ENABLE(win);
val &= ~SHADOWCON_WINx_PROTECT(win);
writel(val, ctx->regs + SHADOWCON);
win_data->enabled = false;
}
static struct exynos_drm_overlay_ops fimd_overlay_ops = {
......@@ -540,9 +545,17 @@ static void fimd_finish_pageflip(struct drm_device *drm_dev, int crtc)
wake_up_interruptible(&e->base.file_priv->event_wait);
}
if (is_checked)
if (is_checked) {
drm_vblank_put(drm_dev, crtc);
/*
* don't off vblank if vblank_disable_allowed is 1,
* because vblank would be off by timer handler.
*/
if (!drm_dev->vblank_disable_allowed)
drm_vblank_off(drm_dev, crtc);
}
spin_unlock_irqrestore(&drm_dev->event_lock, flags);
}
......@@ -560,19 +573,14 @@ static irqreturn_t fimd_irq_handler(int irq, void *dev_id)
/* VSYNC interrupt */
writel(VIDINTCON1_INT_FRAME, ctx->regs + VIDINTCON1);
/*
* in case that vblank_disable_allowed is 1, it could induce
* the problem that manager->pipe could be -1 because with
* disable callback, vsync interrupt isn't disabled and at this moment,
* vsync interrupt could occur. the vsync interrupt would be disabled
* by timer handler later.
*/
if (manager->pipe == -1)
return IRQ_HANDLED;
/* check the crtc is detached already from encoder */
if (manager->pipe < 0)
goto out;
drm_handle_vblank(drm_dev, manager->pipe);
fimd_finish_pageflip(drm_dev, manager->pipe);
out:
return IRQ_HANDLED;
}
......@@ -590,6 +598,13 @@ static int fimd_subdrv_probe(struct drm_device *drm_dev, struct device *dev)
*/
drm_dev->irq_enabled = 1;
/*
* with vblank_disable_allowed = 1, vblank interrupt will be disabled
* by drm timer once a current process gives up ownership of
* vblank event.(after drm_vblank_put function is called)
*/
drm_dev->vblank_disable_allowed = 1;
return 0;
}
......@@ -739,15 +754,15 @@ static int __devinit fimd_probe(struct platform_device *pdev)
ctx->irq = res->start;
for (win = 0; win < WINDOWS_NR; win++)
fimd_clear_win(ctx, win);
ret = request_irq(ctx->irq, fimd_irq_handler, 0, "drm_fimd", ctx);
if (ret < 0) {
dev_err(dev, "irq request failed.\n");
goto err_req_irq;
}
for (win = 0; win < WINDOWS_NR; win++)
fimd_clear_win(ctx, win);
ctx->clkdiv = fimd_calc_clkdiv(ctx, timing);
ctx->vidcon0 = pdata->vidcon0;
ctx->vidcon1 = pdata->vidcon1;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment