diff options
Diffstat (limited to 'drivers/gpu/drm/exynos')
28 files changed, 4357 insertions, 411 deletions
diff --git a/drivers/gpu/drm/exynos/Kconfig b/drivers/gpu/drm/exynos/Kconfig index 847466aab435..f9aaa56eae07 100644 --- a/drivers/gpu/drm/exynos/Kconfig +++ b/drivers/gpu/drm/exynos/Kconfig @@ -18,3 +18,10 @@ config DRM_EXYNOS_FIMD help Choose this option if you want to use Exynos FIMD for DRM. If M is selected, the module will be called exynos_drm_fimd + +config DRM_EXYNOS_HDMI + tristate "Exynos DRM HDMI" + depends on DRM_EXYNOS + help + Choose this option if you want to use Exynos HDMI for DRM. + If M is selected, the module will be called exynos_drm_hdmi diff --git a/drivers/gpu/drm/exynos/Makefile b/drivers/gpu/drm/exynos/Makefile index 0496d3ff2683..395e69c9a96e 100644 --- a/drivers/gpu/drm/exynos/Makefile +++ b/drivers/gpu/drm/exynos/Makefile @@ -5,7 +5,10 @@ ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/exynos exynosdrm-y := exynos_drm_drv.o exynos_drm_encoder.o exynos_drm_connector.o \ exynos_drm_crtc.o exynos_drm_fbdev.o exynos_drm_fb.o \ - exynos_drm_buf.o exynos_drm_gem.o exynos_drm_core.o + exynos_drm_buf.o exynos_drm_gem.o exynos_drm_core.o \ + exynos_drm_plane.o obj-$(CONFIG_DRM_EXYNOS) += exynosdrm.o obj-$(CONFIG_DRM_EXYNOS_FIMD) += exynos_drm_fimd.o +obj-$(CONFIG_DRM_EXYNOS_HDMI) += exynos_hdmi.o exynos_mixer.o exynos_ddc.o \ + exynos_hdmiphy.o exynos_drm_hdmi.o diff --git a/drivers/gpu/drm/exynos/exynos_ddc.c b/drivers/gpu/drm/exynos/exynos_ddc.c new file mode 100644 index 000000000000..84b614fe26fd --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_ddc.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Authors: + * Seung-Woo Kim <sw0312.kim@samsung.com> + * Inki Dae <inki.dae@samsung.com> + * + * 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. + * + */ + +#include "drmP.h" + +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/module.h> + + +#include "exynos_drm_drv.h" +#include "exynos_hdmi.h" + +static int s5p_ddc_probe(struct i2c_client *client, + const struct i2c_device_id *dev_id) +{ + hdmi_attach_ddc_client(client); + + dev_info(&client->adapter->dev, "attached s5p_ddc " + "into i2c adapter successfully\n"); + + return 0; +} + +static int s5p_ddc_remove(struct i2c_client *client) +{ + dev_info(&client->adapter->dev, "detached s5p_ddc " + "from i2c adapter successfully\n"); + + return 0; +} + +static struct i2c_device_id ddc_idtable[] = { + {"s5p_ddc", 0}, + { }, +}; + +struct i2c_driver ddc_driver = { + .driver = { + .name = "s5p_ddc", + .owner = THIS_MODULE, + }, + .id_table = ddc_idtable, + .probe = s5p_ddc_probe, + .remove = __devexit_p(s5p_ddc_remove), + .command = NULL, +}; +EXPORT_SYMBOL(ddc_driver); diff --git a/drivers/gpu/drm/exynos/exynos_drm_buf.c b/drivers/gpu/drm/exynos/exynos_drm_buf.c index 2bb07bca511a..3cf785c58186 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_buf.c +++ b/drivers/gpu/drm/exynos/exynos_drm_buf.c @@ -73,7 +73,7 @@ struct exynos_drm_gem_buf *exynos_drm_buf_create(struct drm_device *dev, buffer = kzalloc(sizeof(*buffer), GFP_KERNEL); if (!buffer) { DRM_ERROR("failed to allocate exynos_drm_gem_buf.\n"); - return ERR_PTR(-ENOMEM); + return NULL; } buffer->size = size; @@ -84,8 +84,7 @@ struct exynos_drm_gem_buf *exynos_drm_buf_create(struct drm_device *dev, */ if (lowlevel_buffer_allocate(dev, buffer) < 0) { kfree(buffer); - buffer = NULL; - return ERR_PTR(-ENOMEM); + return NULL; } return buffer; diff --git a/drivers/gpu/drm/exynos/exynos_drm_buf.h b/drivers/gpu/drm/exynos/exynos_drm_buf.h index 6e91f9caa5db..c913f2bad760 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_buf.h +++ b/drivers/gpu/drm/exynos/exynos_drm_buf.h @@ -30,9 +30,6 @@ struct exynos_drm_gem_buf *exynos_drm_buf_create(struct drm_device *dev, unsigned int size); -/* get memory information of a drm framebuffer. */ -struct exynos_drm_gem_buf *exynos_drm_fb_get_buf(struct drm_framebuffer *fb); - /* remove allocated physical memory. */ void exynos_drm_buf_destroy(struct drm_device *dev, struct exynos_drm_gem_buf *buffer); diff --git a/drivers/gpu/drm/exynos/exynos_drm_crtc.c b/drivers/gpu/drm/exynos/exynos_drm_crtc.c index ee43cc220853..e3861ac49295 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_crtc.c +++ b/drivers/gpu/drm/exynos/exynos_drm_crtc.c @@ -34,7 +34,6 @@ #include "exynos_drm_fb.h" #include "exynos_drm_encoder.h" #include "exynos_drm_gem.h" -#include "exynos_drm_buf.h" #define to_exynos_crtc(x) container_of(x, struct exynos_drm_crtc,\ drm_crtc) @@ -52,11 +51,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) @@ -78,19 +79,23 @@ int exynos_drm_overlay_update(struct exynos_drm_overlay *overlay, struct exynos_drm_gem_buf *buffer; unsigned int actual_w; unsigned int actual_h; + int nr = exynos_drm_format_num_buffers(fb->pixel_format); + int i; + + for (i = 0; i < nr; i++) { + buffer = exynos_drm_fb_buffer(fb, i); + if (!buffer) { + DRM_LOG_KMS("buffer is null\n"); + return -EFAULT; + } - buffer = exynos_drm_fb_get_buf(fb); - if (!buffer) { - DRM_LOG_KMS("buffer is null.\n"); - return -EFAULT; - } - - overlay->dma_addr = buffer->dma_addr; - overlay->vaddr = buffer->kvaddr; + overlay->dma_addr[i] = buffer->dma_addr; + overlay->vaddr[i] = buffer->kvaddr; - DRM_DEBUG_KMS("vaddr = 0x%lx, dma_addr = 0x%lx\n", - (unsigned long)overlay->vaddr, - (unsigned long)overlay->dma_addr); + DRM_DEBUG_KMS("buffer: %d, vaddr = 0x%lx, dma_addr = 0x%lx\n", + i, (unsigned long)overlay->vaddr[i], + (unsigned long)overlay->dma_addr[i]); + } actual_w = min((mode->hdisplay - pos->crtc_x), pos->crtc_w); actual_h = min((mode->vdisplay - pos->crtc_y), pos->crtc_h); @@ -101,7 +106,8 @@ int exynos_drm_overlay_update(struct exynos_drm_overlay *overlay, overlay->fb_width = fb->width; overlay->fb_height = fb->height; overlay->bpp = fb->bits_per_pixel; - overlay->pitch = fb->pitch; + overlay->pitch = fb->pitches[0]; + overlay->pixel_format = fb->pixel_format; /* set overlay range to be displayed. */ overlay->crtc_x = pos->crtc_x; @@ -153,26 +159,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 +205,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 +383,8 @@ int exynos_drm_crtc_create(struct drm_device *dev, unsigned int nr) } exynos_crtc->pipe = nr; + exynos_crtc->dpms = DRM_MODE_DPMS_OFF; + exynos_crtc->overlay.zpos = DEFAULT_ZPOS; crtc = &exynos_crtc->drm_crtc; private->crtc[nr] = crtc; @@ -357,9 +398,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 +415,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); } diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c index 53e2216de61d..35889ca255e9 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.c +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c @@ -36,13 +36,16 @@ #include "exynos_drm_fbdev.h" #include "exynos_drm_fb.h" #include "exynos_drm_gem.h" +#include "exynos_drm_plane.h" -#define DRIVER_NAME "exynos-drm" +#define DRIVER_NAME "exynos" #define DRIVER_DESC "Samsung SoC DRM" #define DRIVER_DATE "20110530" #define DRIVER_MAJOR 1 #define DRIVER_MINOR 0 +#define VBLANK_OFF_DELAY 50000 + static int exynos_drm_load(struct drm_device *dev, unsigned long flags) { struct exynos_drm_private *private; @@ -77,6 +80,12 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags) goto err_crtc; } + for (nr = 0; nr < MAX_PLANE; nr++) { + ret = exynos_plane_init(dev, nr); + if (ret) + goto err_crtc; + } + ret = drm_vblank_init(dev, MAX_CRTC); if (ret) goto err_crtc; @@ -100,6 +109,8 @@ static int exynos_drm_load(struct drm_device *dev, unsigned long flags) goto err_drm_device; } + drm_vblank_offdelay = VBLANK_OFF_DELAY; + return 0; err_drm_device: @@ -163,6 +174,18 @@ static struct drm_ioctl_desc exynos_ioctls[] = { DRM_AUTH), DRM_IOCTL_DEF_DRV(EXYNOS_GEM_MMAP, exynos_drm_gem_mmap_ioctl, DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(EXYNOS_PLANE_SET_ZPOS, exynos_plane_set_zpos_ioctl, + DRM_UNLOCKED | DRM_AUTH), +}; + +static const struct file_operations exynos_drm_driver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .mmap = exynos_drm_gem_mmap, + .poll = drm_poll, + .read = drm_read, + .unlocked_ioctl = drm_ioctl, + .release = drm_release, }; static struct drm_driver exynos_drm_driver = { @@ -182,15 +205,7 @@ static struct drm_driver exynos_drm_driver = { .dumb_map_offset = exynos_drm_gem_dumb_map_offset, .dumb_destroy = exynos_drm_gem_dumb_destroy, .ioctls = exynos_ioctls, - .fops = { - .owner = THIS_MODULE, - .open = drm_open, - .mmap = exynos_drm_gem_mmap, - .poll = drm_poll, - .read = drm_read, - .unlocked_ioctl = drm_ioctl, - .release = drm_release, - }, + .fops = &exynos_drm_driver_fops, .name = DRIVER_NAME, .desc = DRIVER_DESC, .date = DRIVER_DATE, diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.h b/drivers/gpu/drm/exynos/exynos_drm_drv.h index 5e02e6ecc2e0..e685e1e33055 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_drv.h +++ b/drivers/gpu/drm/exynos/exynos_drm_drv.h @@ -33,11 +33,16 @@ #include "drm.h" #define MAX_CRTC 2 +#define MAX_PLANE 5 +#define MAX_FB_BUFFER 3 +#define DEFAULT_ZPOS -1 struct drm_device; struct exynos_drm_overlay; struct drm_connector; +extern unsigned int drm_vblank_offdelay; + /* this enumerates display type. */ enum exynos_drm_output_type { EXYNOS_DISPLAY_TYPE_NONE, @@ -57,8 +62,8 @@ enum exynos_drm_output_type { struct exynos_drm_overlay_ops { void (*mode_set)(struct device *subdrv_dev, struct exynos_drm_overlay *overlay); - void (*commit)(struct device *subdrv_dev); - void (*disable)(struct device *subdrv_dev); + void (*commit)(struct device *subdrv_dev, int zpos); + void (*disable)(struct device *subdrv_dev, int zpos); }; /* @@ -80,9 +85,11 @@ struct exynos_drm_overlay_ops { * @scan_flag: interlace or progressive way. * (it could be DRM_MODE_FLAG_*) * @bpp: pixel size.(in bit) - * @dma_addr: bus(accessed by dma) address to the memory region allocated - * for a overlay. - * @vaddr: virtual memory addresss to this overlay. + * @pixel_format: fourcc pixel format of this overlay + * @dma_addr: array of bus(accessed by dma) address to the memory region + * allocated for a overlay. + * @vaddr: array of virtual memory addresss to this overlay. + * @zpos: order of overlay layer(z position). * @default_win: a window to be enabled. * @color_key: color key on or off. * @index_color: if using color key feature then this value would be used @@ -109,8 +116,10 @@ struct exynos_drm_overlay { unsigned int scan_flag; unsigned int bpp; unsigned int pitch; - dma_addr_t dma_addr; - void __iomem *vaddr; + uint32_t pixel_format; + dma_addr_t dma_addr[MAX_FB_BUFFER]; + void __iomem *vaddr[MAX_FB_BUFFER]; + int zpos; bool default_win; bool color_key; @@ -144,17 +153,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); }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_encoder.c b/drivers/gpu/drm/exynos/exynos_drm_encoder.c index 153061415baf..86b93dde219a 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_encoder.c +++ b/drivers/gpu/drm/exynos/exynos_drm_encoder.c @@ -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; @@ -275,12 +294,27 @@ void exynos_drm_disable_vblank(struct drm_encoder *encoder, void *data) manager_ops->disable_vblank(manager->dev); } -void exynos_drm_encoder_crtc_commit(struct drm_encoder *encoder, void *data) +void exynos_drm_encoder_crtc_plane_commit(struct drm_encoder *encoder, + void *data) { struct exynos_drm_manager *manager = to_exynos_encoder(encoder)->manager; struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops; + int zpos = DEFAULT_ZPOS; + + if (data) + zpos = *(int *)data; + + if (overlay_ops && overlay_ops->commit) + overlay_ops->commit(manager->dev, zpos); +} + +void exynos_drm_encoder_crtc_commit(struct drm_encoder *encoder, void *data) +{ + struct exynos_drm_manager *manager = + to_exynos_encoder(encoder)->manager; int crtc = *(int *)data; + int zpos = DEFAULT_ZPOS; DRM_DEBUG_KMS("%s\n", __FILE__); @@ -290,8 +324,53 @@ void exynos_drm_encoder_crtc_commit(struct drm_encoder *encoder, void *data) */ manager->pipe = crtc; - if (overlay_ops && overlay_ops->commit) - overlay_ops->commit(manager->dev); + exynos_drm_encoder_crtc_plane_commit(encoder, &zpos); +} + +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) @@ -310,19 +389,15 @@ void exynos_drm_encoder_crtc_disable(struct drm_encoder *encoder, void *data) struct exynos_drm_manager *manager = to_exynos_encoder(encoder)->manager; struct exynos_drm_overlay_ops *overlay_ops = manager->overlay_ops; + int zpos = DEFAULT_ZPOS; DRM_DEBUG_KMS("\n"); - if (overlay_ops && overlay_ops->disable) - overlay_ops->disable(manager->dev); + if (data) + zpos = *(int *)data; - /* - * 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; + if (overlay_ops && overlay_ops->disable) + overlay_ops->disable(manager->dev, zpos); } MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_encoder.h b/drivers/gpu/drm/exynos/exynos_drm_encoder.h index a22acfbf0e4e..97b087a51cb6 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_encoder.h +++ b/drivers/gpu/drm/exynos/exynos_drm_encoder.h @@ -39,7 +39,12 @@ void exynos_drm_fn_encoder(struct drm_crtc *crtc, void *data, void (*fn)(struct drm_encoder *, void *)); 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_plane_commit(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); diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.c b/drivers/gpu/drm/exynos/exynos_drm_fb.c index 5bf4a1ac7f82..3733fe6723d3 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fb.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fb.c @@ -33,7 +33,6 @@ #include "exynos_drm_drv.h" #include "exynos_drm_fb.h" -#include "exynos_drm_buf.h" #include "exynos_drm_gem.h" #define to_exynos_fb(x) container_of(x, struct exynos_drm_fb, fb) @@ -42,15 +41,11 @@ * exynos specific framebuffer structure. * * @fb: drm framebuffer obejct. - * @exynos_gem_obj: exynos specific gem object containing a gem object. - * @buffer: pointer to exynos_drm_gem_buffer object. - * - contain the memory information to memory region allocated - * at default framebuffer creation. + * @exynos_gem_obj: array of exynos specific gem object containing a gem object. */ struct exynos_drm_fb { struct drm_framebuffer fb; - struct exynos_drm_gem_obj *exynos_gem_obj; - struct exynos_drm_gem_buf *buffer; + struct exynos_drm_gem_obj *exynos_gem_obj[MAX_FB_BUFFER]; }; static void exynos_drm_fb_destroy(struct drm_framebuffer *fb) @@ -61,13 +56,6 @@ static void exynos_drm_fb_destroy(struct drm_framebuffer *fb) drm_framebuffer_cleanup(fb); - /* - * default framebuffer has no gem object so - * a buffer of the default framebuffer should be released at here. - */ - if (!exynos_fb->exynos_gem_obj && exynos_fb->buffer) - exynos_drm_buf_destroy(fb->dev, exynos_fb->buffer); - kfree(exynos_fb); exynos_fb = NULL; } @@ -81,7 +69,7 @@ static int exynos_drm_fb_create_handle(struct drm_framebuffer *fb, DRM_DEBUG_KMS("%s\n", __FILE__); return drm_gem_handle_create(file_priv, - &exynos_fb->exynos_gem_obj->base, handle); + &exynos_fb->exynos_gem_obj[0]->base, handle); } static int exynos_drm_fb_dirty(struct drm_framebuffer *fb, @@ -102,134 +90,88 @@ static struct drm_framebuffer_funcs exynos_drm_fb_funcs = { .dirty = exynos_drm_fb_dirty, }; -static struct drm_framebuffer * -exynos_drm_fb_init(struct drm_file *file_priv, struct drm_device *dev, - struct drm_mode_fb_cmd *mode_cmd) +struct drm_framebuffer * +exynos_drm_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object *obj) { struct exynos_drm_fb *exynos_fb; - struct drm_framebuffer *fb; - struct exynos_drm_gem_obj *exynos_gem_obj = NULL; - struct drm_gem_object *obj; - unsigned int size; int ret; - DRM_DEBUG_KMS("%s\n", __FILE__); - - mode_cmd->pitch = max(mode_cmd->pitch, - mode_cmd->width * (mode_cmd->bpp >> 3)); - - DRM_LOG_KMS("drm fb create(%dx%d)\n", - mode_cmd->width, mode_cmd->height); - exynos_fb = kzalloc(sizeof(*exynos_fb), GFP_KERNEL); if (!exynos_fb) { - DRM_ERROR("failed to allocate exynos drm framebuffer.\n"); + DRM_ERROR("failed to allocate exynos drm framebuffer\n"); return ERR_PTR(-ENOMEM); } - fb = &exynos_fb->fb; - ret = drm_framebuffer_init(dev, fb, &exynos_drm_fb_funcs); + ret = drm_framebuffer_init(dev, &exynos_fb->fb, &exynos_drm_fb_funcs); if (ret) { - DRM_ERROR("failed to initialize framebuffer.\n"); - goto err_init; + DRM_ERROR("failed to initialize framebuffer\n"); + return ERR_PTR(ret); } - DRM_LOG_KMS("create: fb id: %d\n", fb->base.id); + drm_helper_mode_fill_fb_struct(&exynos_fb->fb, mode_cmd); + exynos_fb->exynos_gem_obj[0] = to_exynos_gem_obj(obj); - size = mode_cmd->pitch * mode_cmd->height; + return &exynos_fb->fb; +} - /* - * mode_cmd->handle could be NULL at booting time or - * with user request. if NULL, a new buffer or a gem object - * would be allocated. - */ - if (!mode_cmd->handle) { - if (!file_priv) { - struct exynos_drm_gem_buf *buffer; - - /* - * in case that file_priv is NULL, it allocates - * only buffer and this buffer would be used - * for default framebuffer. - */ - buffer = exynos_drm_buf_create(dev, size); - if (IS_ERR(buffer)) { - ret = PTR_ERR(buffer); - goto err_buffer; - } - - exynos_fb->buffer = buffer; - - DRM_LOG_KMS("default: dma_addr = 0x%lx, size = 0x%x\n", - (unsigned long)buffer->dma_addr, size); - - goto out; - } else { - exynos_gem_obj = exynos_drm_gem_create(dev, file_priv, - &mode_cmd->handle, - size); - if (IS_ERR(exynos_gem_obj)) { - ret = PTR_ERR(exynos_gem_obj); - goto err_buffer; - } - } - } else { - obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handle); - if (!obj) { - DRM_ERROR("failed to lookup gem object.\n"); - goto err_buffer; - } +static struct drm_framebuffer * +exynos_user_fb_create(struct drm_device *dev, struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct drm_gem_object *obj; + struct drm_framebuffer *fb; + struct exynos_drm_fb *exynos_fb; + int nr; + int i; - exynos_gem_obj = to_exynos_gem_obj(obj); + DRM_DEBUG_KMS("%s\n", __FILE__); - drm_gem_object_unreference_unlocked(obj); + obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]); + if (!obj) { + DRM_ERROR("failed to lookup gem object\n"); + return ERR_PTR(-ENOENT); } - /* - * if got a exynos_gem_obj from either a handle or - * a new creation then exynos_fb->exynos_gem_obj is NULL - * so that default framebuffer has no its own gem object, - * only its own buffer object. - */ - exynos_fb->buffer = exynos_gem_obj->buffer; - - DRM_LOG_KMS("dma_addr = 0x%lx, size = 0x%x, gem object = 0x%x\n", - (unsigned long)exynos_fb->buffer->dma_addr, size, - (unsigned int)&exynos_gem_obj->base); + drm_gem_object_unreference_unlocked(obj); -out: - exynos_fb->exynos_gem_obj = exynos_gem_obj; + fb = exynos_drm_framebuffer_init(dev, mode_cmd, obj); + if (IS_ERR(fb)) + return fb; - drm_helper_mode_fill_fb_struct(fb, mode_cmd); + exynos_fb = to_exynos_fb(fb); + nr = exynos_drm_format_num_buffers(fb->pixel_format); - return fb; - -err_buffer: - drm_framebuffer_cleanup(fb); - -err_init: - kfree(exynos_fb); + for (i = 1; i < nr; i++) { + obj = drm_gem_object_lookup(dev, file_priv, + mode_cmd->handles[i]); + if (!obj) { + DRM_ERROR("failed to lookup gem object\n"); + exynos_drm_fb_destroy(fb); + return ERR_PTR(-ENOENT); + } - return ERR_PTR(ret); -} + drm_gem_object_unreference_unlocked(obj); -struct drm_framebuffer *exynos_drm_fb_create(struct drm_device *dev, - struct drm_file *file_priv, - struct drm_mode_fb_cmd *mode_cmd) -{ - DRM_DEBUG_KMS("%s\n", __FILE__); + exynos_fb->exynos_gem_obj[i] = to_exynos_gem_obj(obj); + } - return exynos_drm_fb_init(file_priv, dev, mode_cmd); + return fb; } -struct exynos_drm_gem_buf *exynos_drm_fb_get_buf(struct drm_framebuffer *fb) +struct exynos_drm_gem_buf *exynos_drm_fb_buffer(struct drm_framebuffer *fb, + int index) { struct exynos_drm_fb *exynos_fb = to_exynos_fb(fb); struct exynos_drm_gem_buf *buffer; DRM_DEBUG_KMS("%s\n", __FILE__); - buffer = exynos_fb->buffer; + if (index >= MAX_FB_BUFFER) + return NULL; + + buffer = exynos_fb->exynos_gem_obj[index]->buffer; if (!buffer) return NULL; @@ -250,7 +192,7 @@ static void exynos_drm_output_poll_changed(struct drm_device *dev) } static struct drm_mode_config_funcs exynos_drm_mode_config_funcs = { - .fb_create = exynos_drm_fb_create, + .fb_create = exynos_user_fb_create, .output_poll_changed = exynos_drm_output_poll_changed, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_fb.h b/drivers/gpu/drm/exynos/exynos_drm_fb.h index eb35931d302c..3ecb30d93552 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fb.h +++ b/drivers/gpu/drm/exynos/exynos_drm_fb.h @@ -28,9 +28,27 @@ #ifndef _EXYNOS_DRM_FB_H_ #define _EXYNOS_DRM_FB_H -struct drm_framebuffer *exynos_drm_fb_create(struct drm_device *dev, - struct drm_file *filp, - struct drm_mode_fb_cmd *mode_cmd); +static inline int exynos_drm_format_num_buffers(uint32_t format) +{ + switch (format) { + case DRM_FORMAT_NV12M: + case DRM_FORMAT_NV12MT: + return 2; + case DRM_FORMAT_YUV420M: + return 3; + default: + return 1; + } +} + +struct drm_framebuffer * +exynos_drm_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object *obj); + +/* get memory information of a drm framebuffer */ +struct exynos_drm_gem_buf *exynos_drm_fb_buffer(struct drm_framebuffer *fb, + int index); void exynos_drm_mode_config_init(struct drm_device *dev); diff --git a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c index 836f41008187..d7ae29d2f3d6 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fbdev.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fbdev.c @@ -34,7 +34,6 @@ #include "exynos_drm_drv.h" #include "exynos_drm_fb.h" #include "exynos_drm_gem.h" -#include "exynos_drm_buf.h" #define MAX_CONNECTOR 4 #define PREFERRED_BPP 32 @@ -43,8 +42,8 @@ drm_fb_helper) struct exynos_drm_fbdev { - struct drm_fb_helper drm_fb_helper; - struct drm_framebuffer *fb; + struct drm_fb_helper drm_fb_helper; + struct exynos_drm_gem_obj *exynos_gem_obj; }; static int exynos_drm_fbdev_set_par(struct fb_info *info) @@ -90,26 +89,24 @@ static int exynos_drm_fbdev_update(struct drm_fb_helper *helper, { struct fb_info *fbi = helper->fbdev; struct drm_device *dev = helper->dev; - struct exynos_drm_fbdev *exynos_fb = to_exynos_fbdev(helper); struct exynos_drm_gem_buf *buffer; unsigned int size = fb->width * fb->height * (fb->bits_per_pixel >> 3); unsigned long offset; DRM_DEBUG_KMS("%s\n", __FILE__); - exynos_fb->fb = fb; - - drm_fb_helper_fill_fix(fbi, fb->pitch, fb->depth); + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height); - buffer = exynos_drm_fb_get_buf(fb); + /* RGB formats use only one buffer */ + buffer = exynos_drm_fb_buffer(fb, 0); if (!buffer) { DRM_LOG_KMS("buffer is null.\n"); return -EFAULT; } offset = fbi->var.xoffset * (fb->bits_per_pixel >> 3); - offset += fbi->var.yoffset * fb->pitch; + offset += fbi->var.yoffset * fb->pitches[0]; dev->mode_config.fb_base = (resource_size_t)buffer->dma_addr; fbi->screen_base = buffer->kvaddr + offset; @@ -124,10 +121,12 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, struct drm_fb_helper_surface_size *sizes) { struct exynos_drm_fbdev *exynos_fbdev = to_exynos_fbdev(helper); + struct exynos_drm_gem_obj *exynos_gem_obj; struct drm_device *dev = helper->dev; struct fb_info *fbi; - struct drm_mode_fb_cmd mode_cmd = { 0 }; + struct drm_mode_fb_cmd2 mode_cmd = { 0 }; struct platform_device *pdev = dev->platformdev; + unsigned long size; int ret; DRM_DEBUG_KMS("%s\n", __FILE__); @@ -138,8 +137,9 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, mode_cmd.width = sizes->surface_width; mode_cmd.height = sizes->surface_height; - mode_cmd.bpp = sizes->surface_bpp; - mode_cmd.depth = sizes->surface_depth; + mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3); + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); mutex_lock(&dev->struct_mutex); @@ -150,14 +150,23 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, goto out; } - exynos_fbdev->fb = exynos_drm_fb_create(dev, NULL, &mode_cmd); - if (IS_ERR_OR_NULL(exynos_fbdev->fb)) { + size = mode_cmd.pitches[0] * mode_cmd.height; + exynos_gem_obj = exynos_drm_gem_create(dev, size); + if (IS_ERR(exynos_gem_obj)) { + ret = PTR_ERR(exynos_gem_obj); + goto out; + } + + exynos_fbdev->exynos_gem_obj = exynos_gem_obj; + + helper->fb = exynos_drm_framebuffer_init(dev, &mode_cmd, + &exynos_gem_obj->base); + if (IS_ERR_OR_NULL(helper->fb)) { DRM_ERROR("failed to create drm framebuffer.\n"); - ret = PTR_ERR(exynos_fbdev->fb); + ret = PTR_ERR(helper->fb); goto out; } - helper->fb = exynos_fbdev->fb; helper->fbdev = fbi; fbi->par = helper; @@ -171,8 +180,10 @@ static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, } ret = exynos_drm_fbdev_update(helper, helper->fb); - if (ret < 0) + if (ret < 0) { fb_dealloc_cmap(&fbi->cmap); + goto out; + } /* * if failed, all resources allocated above would be released by @@ -205,34 +216,42 @@ static int exynos_drm_fbdev_recreate(struct drm_fb_helper *helper, { struct drm_device *dev = helper->dev; struct exynos_drm_fbdev *exynos_fbdev = to_exynos_fbdev(helper); - struct drm_framebuffer *fb = exynos_fbdev->fb; - struct drm_mode_fb_cmd mode_cmd = { 0 }; + struct exynos_drm_gem_obj *exynos_gem_obj; + struct drm_framebuffer *fb = helper->fb; + struct drm_mode_fb_cmd2 mode_cmd = { 0 }; + unsigned long size; DRM_DEBUG_KMS("%s\n", __FILE__); - if (helper->fb != fb) { - DRM_ERROR("drm framebuffer is different\n"); - return -EINVAL; - } - if (exynos_drm_fbdev_is_samefb(fb, sizes)) return 0; mode_cmd.width = sizes->surface_width; mode_cmd.height = sizes->surface_height; - mode_cmd.bpp = sizes->surface_bpp; - mode_cmd.depth = sizes->surface_depth; + mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3); + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + + if (exynos_fbdev->exynos_gem_obj) + exynos_drm_gem_destroy(exynos_fbdev->exynos_gem_obj); if (fb->funcs->destroy) fb->funcs->destroy(fb); - exynos_fbdev->fb = exynos_drm_fb_create(dev, NULL, &mode_cmd); - if (IS_ERR(exynos_fbdev->fb)) { - DRM_ERROR("failed to allocate fb.\n"); - return PTR_ERR(exynos_fbdev->fb); + size = mode_cmd.pitches[0] * mode_cmd.height; + exynos_gem_obj = exynos_drm_gem_create(dev, size); + if (IS_ERR(exynos_gem_obj)) + return PTR_ERR(exynos_gem_obj); + + exynos_fbdev->exynos_gem_obj = exynos_gem_obj; + + helper->fb = exynos_drm_framebuffer_init(dev, &mode_cmd, + &exynos_gem_obj->base); + if (IS_ERR_OR_NULL(helper->fb)) { + DRM_ERROR("failed to create drm framebuffer.\n"); + return PTR_ERR(helper->fb); } - helper->fb = exynos_fbdev->fb; return exynos_drm_fbdev_update(helper, helper->fb); } @@ -366,6 +385,9 @@ void exynos_drm_fbdev_fini(struct drm_device *dev) fbdev = to_exynos_fbdev(private->fb_helper); + if (fbdev->exynos_gem_obj) + exynos_drm_gem_destroy(fbdev->exynos_gem_obj); + exynos_drm_fbdev_destroy(dev, private->fb_helper); kfree(fbdev); private->fb_helper = NULL; diff --git a/drivers/gpu/drm/exynos/exynos_drm_fimd.c b/drivers/gpu/drm/exynos/exynos_drm_fimd.c index db3b3d9e731d..ca83139cd309 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_fimd.c +++ b/drivers/gpu/drm/exynos/exynos_drm_fimd.c @@ -17,6 +17,7 @@ #include <linux/module.h> #include <linux/platform_device.h> #include <linux/clk.h> +#include <linux/pm_runtime.h> #include <drm/exynos_drm.h> #include <plat/regs-fb-v4.h> @@ -68,6 +69,7 @@ struct fimd_win_data { void __iomem *vaddr; unsigned int buf_offsize; unsigned int line_size; /* bytes */ + bool enabled; }; struct fimd_context { @@ -84,6 +86,8 @@ struct fimd_context { unsigned long irq_flags; u32 vidcon0; u32 vidcon1; + bool suspended; + struct mutex lock; struct fb_videomode *timing; }; @@ -119,7 +123,7 @@ static int fimd_display_power_on(struct device *dev, int mode) { DRM_DEBUG_KMS("%s\n", __FILE__); - /* TODO. */ + /* TODO */ return 0; } @@ -132,12 +136,68 @@ 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) +{ + struct fimd_context *ctx = get_fimd_context(subdrv_dev); + + DRM_DEBUG_KMS("%s, %d\n", __FILE__, mode); + + mutex_lock(&ctx->lock); + + switch (mode) { + case DRM_MODE_DPMS_ON: + /* + * enable fimd hardware only if suspended status. + * + * P.S. fimd_dpms function would be called at booting time so + * clk_enable could be called double time. + */ + if (ctx->suspended) + pm_runtime_get_sync(subdrv_dev); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + pm_runtime_put_sync(subdrv_dev); + break; + default: + DRM_DEBUG_KMS("unspecified mode %d\n", mode); + break; + } + + mutex_unlock(&ctx->lock); +} + +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; + int i; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + for (i = 0; i < WINDOWS_NR; i++) { + win_data = &ctx->win_data[i]; + if (win_data->enabled && (ovl_ops && ovl_ops->commit)) + ovl_ops->commit(subdrv_dev, i); + } + + 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); struct fb_videomode *timing = ctx->timing; u32 val; + if (ctx->suspended) + return; + DRM_DEBUG_KMS("%s\n", __FILE__); /* setup polarity values from machine code. */ @@ -177,40 +237,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); @@ -218,6 +244,9 @@ static int fimd_enable_vblank(struct device *dev) DRM_DEBUG_KMS("%s\n", __FILE__); + if (ctx->suspended) + return -EPERM; + if (!test_and_set_bit(0, &ctx->irq_flags)) { val = readl(ctx->regs + VIDINTCON0); @@ -242,6 +271,9 @@ static void fimd_disable_vblank(struct device *dev) DRM_DEBUG_KMS("%s\n", __FILE__); + if (ctx->suspended) + return; + if (test_and_clear_bit(0, &ctx->irq_flags)) { val = readl(ctx->regs + VIDINTCON0); @@ -253,8 +285,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, }; @@ -264,6 +297,7 @@ static void fimd_win_mode_set(struct device *dev, { struct fimd_context *ctx = get_fimd_context(dev); struct fimd_win_data *win_data; + int win; unsigned long offset; DRM_DEBUG_KMS("%s\n", __FILE__); @@ -273,12 +307,19 @@ static void fimd_win_mode_set(struct device *dev, return; } + win = overlay->zpos; + if (win == DEFAULT_ZPOS) + win = ctx->default_win; + + if (win < 0 || win > WINDOWS_NR) + return; + offset = overlay->fb_x * (overlay->bpp >> 3); offset += overlay->fb_y * overlay->pitch; DRM_DEBUG_KMS("offset = 0x%lx, pitch = %x\n", offset, overlay->pitch); - win_data = &ctx->win_data[ctx->default_win]; + win_data = &ctx->win_data[win]; win_data->offset_x = overlay->crtc_x; win_data->offset_y = overlay->crtc_y; @@ -286,8 +327,8 @@ static void fimd_win_mode_set(struct device *dev, win_data->ovl_height = overlay->crtc_height; win_data->fb_width = overlay->fb_width; win_data->fb_height = overlay->fb_height; - win_data->dma_addr = overlay->dma_addr + offset; - win_data->vaddr = overlay->vaddr + offset; + win_data->dma_addr = overlay->dma_addr[0] + offset; + win_data->vaddr = overlay->vaddr[0] + offset; win_data->bpp = overlay->bpp; win_data->buf_offsize = (overlay->fb_width - overlay->crtc_width) * (overlay->bpp >> 3); @@ -381,15 +422,21 @@ static void fimd_win_set_colkey(struct device *dev, unsigned int win) writel(keycon1, ctx->regs + WKEYCON1_BASE(win)); } -static void fimd_win_commit(struct device *dev) +static void fimd_win_commit(struct device *dev, int zpos) { struct fimd_context *ctx = get_fimd_context(dev); struct fimd_win_data *win_data; - int win = ctx->default_win; + int win = zpos; unsigned long val, alpha, size; DRM_DEBUG_KMS("%s\n", __FILE__); + if (ctx->suspended) + return; + + if (win == DEFAULT_ZPOS) + win = ctx->default_win; + if (win < 0 || win > WINDOWS_NR) return; @@ -472,24 +519,37 @@ 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) +static void fimd_win_disable(struct device *dev, int zpos) { struct fimd_context *ctx = get_fimd_context(dev); - int win = ctx->default_win; + struct fimd_win_data *win_data; + int win = zpos; u32 val; DRM_DEBUG_KMS("%s\n", __FILE__); + if (win == DEFAULT_ZPOS) + win = ctx->default_win; + 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 +565,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 +602,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 +630,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 +655,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,9 +811,6 @@ 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"); @@ -769,7 +838,17 @@ static int __devinit fimd_probe(struct platform_device *pdev) subdrv->manager.display_ops = &fimd_display_ops; subdrv->manager.dev = dev; + mutex_init(&ctx->lock); + platform_set_drvdata(pdev, ctx); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_get_sync(dev); + + for (win = 0; win < WINDOWS_NR; win++) + fimd_clear_win(ctx, win); + exynos_drm_subdrv_register(subdrv); return 0; @@ -797,14 +876,25 @@ err_clk_get: static int __devexit fimd_remove(struct platform_device *pdev) { + struct device *dev = &pdev->dev; struct fimd_context *ctx = platform_get_drvdata(pdev); DRM_DEBUG_KMS("%s\n", __FILE__); exynos_drm_subdrv_unregister(&ctx->subdrv); + if (ctx->suspended) + goto out; + clk_disable(ctx->lcd_clk); clk_disable(ctx->bus_clk); + + pm_runtime_set_suspended(dev); + pm_runtime_put_sync(dev); + +out: + pm_runtime_disable(dev); + clk_put(ctx->lcd_clk); clk_put(ctx->bus_clk); @@ -818,12 +908,102 @@ static int __devexit fimd_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_SLEEP +static int fimd_suspend(struct device *dev) +{ + int ret; + + if (pm_runtime_suspended(dev)) + return 0; + + ret = pm_runtime_suspend(dev); + if (ret < 0) + return ret; + + return 0; +} + +static int fimd_resume(struct device *dev) +{ + int ret; + + ret = pm_runtime_resume(dev); + if (ret < 0) { + DRM_ERROR("failed to resume runtime pm.\n"); + return ret; + } + + pm_runtime_disable(dev); + + ret = pm_runtime_set_active(dev); + if (ret < 0) { + DRM_ERROR("failed to active runtime pm.\n"); + pm_runtime_enable(dev); + pm_runtime_suspend(dev); + return ret; + } + + pm_runtime_enable(dev); + + return 0; +} +#endif + +#ifdef CONFIG_PM_RUNTIME +static int fimd_runtime_suspend(struct device *dev) +{ + struct fimd_context *ctx = get_fimd_context(dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + clk_disable(ctx->lcd_clk); + clk_disable(ctx->bus_clk); + + ctx->suspended = true; + return 0; +} + +static int fimd_runtime_resume(struct device *dev) +{ + struct fimd_context *ctx = get_fimd_context(dev); + int ret; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + ret = clk_enable(ctx->bus_clk); + if (ret < 0) + return ret; + + ret = clk_enable(ctx->lcd_clk); + if (ret < 0) { + clk_disable(ctx->bus_clk); + return ret; + } + + ctx->suspended = false; + + /* if vblank was enabled status, enable it again. */ + if (test_and_clear_bit(0, &ctx->irq_flags)) + fimd_enable_vblank(dev); + + fimd_apply(dev); + + return 0; +} +#endif + +static const struct dev_pm_ops fimd_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fimd_suspend, fimd_resume) + SET_RUNTIME_PM_OPS(fimd_runtime_suspend, fimd_runtime_resume, NULL) +}; + static struct platform_driver fimd_driver = { .probe = fimd_probe, .remove = __devexit_p(fimd_remove), .driver = { .name = "exynos4-fb", .owner = THIS_MODULE, + .pm = &fimd_pm_ops, }, }; diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c index aba0fe47f7ea..025abb3e3b67 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.c +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c @@ -55,17 +55,54 @@ static unsigned int convert_to_vm_err_msg(int msg) return out_msg; } -static unsigned int get_gem_mmap_offset(struct drm_gem_object *obj) +static int exynos_drm_gem_handle_create(struct drm_gem_object *obj, + struct drm_file *file_priv, + unsigned int *handle) { + int ret; + + /* + * allocate a id of idr table where the obj is registered + * and handle has the id what user can see. + */ + ret = drm_gem_handle_create(file_priv, obj, handle); + if (ret) + return ret; + + DRM_DEBUG_KMS("gem handle = 0x%x\n", *handle); + + /* drop reference from allocate - handle holds it now. */ + drm_gem_object_unreference_unlocked(obj); + + return 0; +} + +void exynos_drm_gem_destroy(struct exynos_drm_gem_obj *exynos_gem_obj) +{ + struct drm_gem_object *obj; + DRM_DEBUG_KMS("%s\n", __FILE__); - return (unsigned int)obj->map_list.hash.key << PAGE_SHIFT; + if (!exynos_gem_obj) + return; + + obj = &exynos_gem_obj->base; + + DRM_DEBUG_KMS("handle count = %d\n", atomic_read(&obj->handle_count)); + + exynos_drm_buf_destroy(obj->dev, exynos_gem_obj->buffer); + + if (obj->map_list.map) + drm_gem_free_mmap_offset(obj); + + /* release file pointer to gem object. */ + drm_gem_object_release(obj); + + kfree(exynos_gem_obj); } -static struct exynos_drm_gem_obj - *exynos_drm_gem_init(struct drm_device *drm_dev, - struct drm_file *file_priv, unsigned int *handle, - unsigned int size) +static struct exynos_drm_gem_obj *exynos_drm_gem_init(struct drm_device *dev, + unsigned long size) { struct exynos_drm_gem_obj *exynos_gem_obj; struct drm_gem_object *obj; @@ -73,75 +110,41 @@ static struct exynos_drm_gem_obj exynos_gem_obj = kzalloc(sizeof(*exynos_gem_obj), GFP_KERNEL); if (!exynos_gem_obj) { - DRM_ERROR("failed to allocate exynos gem object.\n"); - return ERR_PTR(-ENOMEM); + DRM_ERROR("failed to allocate exynos gem object\n"); + return NULL; } obj = &exynos_gem_obj->base; - ret = drm_gem_object_init(drm_dev, obj, size); + ret = drm_gem_object_init(dev, obj, size); if (ret < 0) { - DRM_ERROR("failed to initialize gem object.\n"); - ret = -EINVAL; - goto err_object_init; + DRM_ERROR("failed to initialize gem object\n"); + kfree(exynos_gem_obj); + return NULL; } DRM_DEBUG_KMS("created file object = 0x%x\n", (unsigned int)obj->filp); - ret = drm_gem_create_mmap_offset(obj); - if (ret < 0) { - DRM_ERROR("failed to allocate mmap offset.\n"); - goto err_create_mmap_offset; - } - - /* - * allocate a id of idr table where the obj is registered - * and handle has the id what user can see. - */ - ret = drm_gem_handle_create(file_priv, obj, handle); - if (ret) - goto err_handle_create; - - DRM_DEBUG_KMS("gem handle = 0x%x\n", *handle); - - /* drop reference from allocate - handle holds it now. */ - drm_gem_object_unreference_unlocked(obj); - return exynos_gem_obj; - -err_handle_create: - drm_gem_free_mmap_offset(obj); - -err_create_mmap_offset: - drm_gem_object_release(obj); - -err_object_init: - kfree(exynos_gem_obj); - - return ERR_PTR(ret); } struct exynos_drm_gem_obj *exynos_drm_gem_create(struct drm_device *dev, - struct drm_file *file_priv, - unsigned int *handle, unsigned long size) + unsigned long size) { - - struct exynos_drm_gem_obj *exynos_gem_obj = NULL; struct exynos_drm_gem_buf *buffer; + struct exynos_drm_gem_obj *exynos_gem_obj; size = roundup(size, PAGE_SIZE); - DRM_DEBUG_KMS("%s: size = 0x%lx\n", __FILE__, size); buffer = exynos_drm_buf_create(dev, size); - if (IS_ERR(buffer)) { - return ERR_CAST(buffer); - } + if (!buffer) + return ERR_PTR(-ENOMEM); - exynos_gem_obj = exynos_drm_gem_init(dev, file_priv, handle, size); - if (IS_ERR(exynos_gem_obj)) { + exynos_gem_obj = exynos_drm_gem_init(dev, size); + if (!exynos_gem_obj) { exynos_drm_buf_destroy(dev, buffer); - return exynos_gem_obj; + return ERR_PTR(-ENOMEM); } exynos_gem_obj->buffer = buffer; @@ -150,23 +153,30 @@ struct exynos_drm_gem_obj *exynos_drm_gem_create(struct drm_device *dev, } int exynos_drm_gem_create_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) + struct drm_file *file_priv) { struct drm_exynos_gem_create *args = data; - struct exynos_drm_gem_obj *exynos_gem_obj = NULL; + struct exynos_drm_gem_obj *exynos_gem_obj; + int ret; DRM_DEBUG_KMS("%s\n", __FILE__); - exynos_gem_obj = exynos_drm_gem_create(dev, file_priv, - &args->handle, args->size); + exynos_gem_obj = exynos_drm_gem_create(dev, args->size); if (IS_ERR(exynos_gem_obj)) return PTR_ERR(exynos_gem_obj); + ret = exynos_drm_gem_handle_create(&exynos_gem_obj->base, file_priv, + &args->handle); + if (ret) { + exynos_drm_gem_destroy(exynos_gem_obj); + return ret; + } + return 0; } int exynos_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) + struct drm_file *file_priv) { struct drm_exynos_gem_map_off *args = data; @@ -185,7 +195,7 @@ int exynos_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data, } static int exynos_drm_gem_mmap_buffer(struct file *filp, - struct vm_area_struct *vma) + struct vm_area_struct *vma) { struct drm_gem_object *obj = filp->private_data; struct exynos_drm_gem_obj *exynos_gem_obj = to_exynos_gem_obj(obj); @@ -196,6 +206,7 @@ static int exynos_drm_gem_mmap_buffer(struct file *filp, vma->vm_flags |= (VM_IO | VM_RESERVED); + /* in case of direct mapping, always having non-cachable attribute */ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); vma->vm_file = filp; @@ -232,7 +243,7 @@ static const struct file_operations exynos_drm_gem_fops = { }; int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv) + struct drm_file *file_priv) { struct drm_exynos_gem_mmap *args = data; struct drm_gem_object *obj; @@ -278,32 +289,19 @@ int exynos_drm_gem_init_object(struct drm_gem_object *obj) return 0; } -void exynos_drm_gem_free_object(struct drm_gem_object *gem_obj) +void exynos_drm_gem_free_object(struct drm_gem_object *obj) { - struct exynos_drm_gem_obj *exynos_gem_obj; - DRM_DEBUG_KMS("%s\n", __FILE__); - DRM_DEBUG_KMS("handle count = %d\n", - atomic_read(&gem_obj->handle_count)); - - if (gem_obj->map_list.map) - drm_gem_free_mmap_offset(gem_obj); - - /* release file pointer to gem object. */ - drm_gem_object_release(gem_obj); - - exynos_gem_obj = to_exynos_gem_obj(gem_obj); - - exynos_drm_buf_destroy(gem_obj->dev, exynos_gem_obj->buffer); - - kfree(exynos_gem_obj); + exynos_drm_gem_destroy(to_exynos_gem_obj(obj)); } int exynos_drm_gem_dumb_create(struct drm_file *file_priv, - struct drm_device *dev, struct drm_mode_create_dumb *args) + struct drm_device *dev, + struct drm_mode_create_dumb *args) { struct exynos_drm_gem_obj *exynos_gem_obj; + int ret; DRM_DEBUG_KMS("%s\n", __FILE__); @@ -316,19 +314,27 @@ int exynos_drm_gem_dumb_create(struct drm_file *file_priv, args->pitch = args->width * args->bpp >> 3; args->size = args->pitch * args->height; - exynos_gem_obj = exynos_drm_gem_create(dev, file_priv, &args->handle, - args->size); + exynos_gem_obj = exynos_drm_gem_create(dev, args->size); if (IS_ERR(exynos_gem_obj)) return PTR_ERR(exynos_gem_obj); + ret = exynos_drm_gem_handle_create(&exynos_gem_obj->base, file_priv, + &args->handle); + if (ret) { + exynos_drm_gem_destroy(exynos_gem_obj); + return ret; + } + return 0; } int exynos_drm_gem_dumb_map_offset(struct drm_file *file_priv, - struct drm_device *dev, uint32_t handle, uint64_t *offset) + struct drm_device *dev, uint32_t handle, + uint64_t *offset) { struct exynos_drm_gem_obj *exynos_gem_obj; struct drm_gem_object *obj; + int ret = 0; DRM_DEBUG_KMS("%s\n", __FILE__); @@ -343,19 +349,46 @@ int exynos_drm_gem_dumb_map_offset(struct drm_file *file_priv, obj = drm_gem_object_lookup(dev, file_priv, handle); if (!obj) { DRM_ERROR("failed to lookup gem object.\n"); - mutex_unlock(&dev->struct_mutex); - return -EINVAL; + ret = -EINVAL; + goto unlock; } exynos_gem_obj = to_exynos_gem_obj(obj); - *offset = get_gem_mmap_offset(&exynos_gem_obj->base); - - drm_gem_object_unreference(obj); + if (!exynos_gem_obj->base.map_list.map) { + ret = drm_gem_create_mmap_offset(&exynos_gem_obj->base); + if (ret) + goto out; + } + *offset = (u64)exynos_gem_obj->base.map_list.hash.key << PAGE_SHIFT; DRM_DEBUG_KMS("offset = 0x%lx\n", (unsigned long)*offset); +out: + drm_gem_object_unreference(obj); +unlock: mutex_unlock(&dev->struct_mutex); + return ret; +} + +int exynos_drm_gem_dumb_destroy(struct drm_file *file_priv, + struct drm_device *dev, + unsigned int handle) +{ + int ret; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + /* + * obj->refcount and obj->handle_count are decreased and + * if both them are 0 then exynos_drm_gem_free_object() + * would be called by callback to release resources. + */ + ret = drm_gem_handle_delete(file_priv, handle); + if (ret < 0) { + DRM_ERROR("failed to delete drm_gem_handle.\n"); + return ret; + } return 0; } @@ -403,28 +436,6 @@ int exynos_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma) return ret; } - -int exynos_drm_gem_dumb_destroy(struct drm_file *file_priv, - struct drm_device *dev, unsigned int handle) -{ - int ret; - - DRM_DEBUG_KMS("%s\n", __FILE__); - - /* - * obj->refcount and obj->handle_count are decreased and - * if both them are 0 then exynos_drm_gem_free_object() - * would be called by callback to release resources. - */ - ret = drm_gem_handle_delete(file_priv, handle); - if (ret < 0) { - DRM_ERROR("failed to delete drm_gem_handle.\n"); - return ret; - } - - return 0; -} - MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); MODULE_DESCRIPTION("Samsung SoC DRM GEM Module"); MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.h b/drivers/gpu/drm/exynos/exynos_drm_gem.h index ef8797334e6d..67cdc9168708 100644 --- a/drivers/gpu/drm/exynos/exynos_drm_gem.h +++ b/drivers/gpu/drm/exynos/exynos_drm_gem.h @@ -60,14 +60,16 @@ struct exynos_drm_gem_buf { * user can access the buffer through kms_bo.handle. */ struct exynos_drm_gem_obj { - struct drm_gem_object base; - struct exynos_drm_gem_buf *buffer; + struct drm_gem_object base; + struct exynos_drm_gem_buf *buffer; }; -/* create a new buffer and get a new gem handle. */ +/* destroy a buffer with gem object */ +void exynos_drm_gem_destroy(struct exynos_drm_gem_obj *exynos_gem_obj); + +/* create a new buffer with gem object */ struct exynos_drm_gem_obj *exynos_drm_gem_create(struct drm_device *dev, - struct drm_file *file_priv, - unsigned int *handle, unsigned long size); + unsigned long size); /* * request gem object creation and buffer allocation as the size @@ -75,15 +77,18 @@ struct exynos_drm_gem_obj *exynos_drm_gem_create(struct drm_device *dev, * height and bpp. */ int exynos_drm_gem_create_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv); + struct drm_file *file_priv); /* get buffer offset to map to user space. */ int exynos_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv); + struct drm_file *file_priv); -/* unmap a buffer from user space. */ -int exynos_drm_gem_munmap_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv); +/* + * mmap the physically continuous memory that a gem object contains + * to user space. + */ +int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); /* initialize gem object. */ int exynos_drm_gem_init_object(struct drm_gem_object *obj); @@ -93,24 +98,13 @@ void exynos_drm_gem_free_object(struct drm_gem_object *gem_obj); /* create memory region for drm framebuffer. */ int exynos_drm_gem_dumb_create(struct drm_file *file_priv, - struct drm_device *dev, struct drm_mode_create_dumb *args); + struct drm_device *dev, + struct drm_mode_create_dumb *args); /* map memory region for drm framebuffer to user space. */ int exynos_drm_gem_dumb_map_offset(struct drm_file *file_priv, - struct drm_device *dev, uint32_t handle, uint64_t *offset); - -/* page fault handler and mmap fault address(virtual) to physical memory. */ -int exynos_drm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); - -/* - * mmap the physically continuous memory that a gem object contains - * to user space. - */ -int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, - struct drm_file *file_priv); - -/* set vm_flags and we can change the vm attribute to other one at here. */ -int exynos_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); + struct drm_device *dev, uint32_t handle, + uint64_t *offset); /* * destroy memory region allocated. @@ -118,6 +112,13 @@ int exynos_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); * would be released by drm_gem_handle_delete(). */ int exynos_drm_gem_dumb_destroy(struct drm_file *file_priv, - struct drm_device *dev, unsigned int handle); + struct drm_device *dev, + unsigned int handle); + +/* page fault handler and mmap fault address(virtual) to physical memory. */ +int exynos_drm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); + +/* set vm_flags and we can change the vm attribute to other one at here. */ +int exynos_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma); #endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.c b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c new file mode 100644 index 000000000000..ed8a319ed84b --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_hdmi.c @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Authors: + * Inki Dae <inki.dae@samsung.com> + * Seung-Woo Kim <sw0312.kim@samsung.com> + * + * 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. + * + */ + +#include "drmP.h" + +#include <linux/kernel.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> + +#include <drm/exynos_drm.h> + +#include "exynos_drm_drv.h" +#include "exynos_drm_hdmi.h" + +#define to_context(dev) platform_get_drvdata(to_platform_device(dev)) +#define to_subdrv(dev) to_context(dev) +#define get_ctx_from_subdrv(subdrv) container_of(subdrv,\ + struct drm_hdmi_context, subdrv); + +/* these callback points shoud be set by specific drivers. */ +static struct exynos_hdmi_display_ops *hdmi_display_ops; +static struct exynos_hdmi_manager_ops *hdmi_manager_ops; +static struct exynos_hdmi_overlay_ops *hdmi_overlay_ops; + +struct drm_hdmi_context { + struct exynos_drm_subdrv subdrv; + struct exynos_drm_hdmi_context *hdmi_ctx; + struct exynos_drm_hdmi_context *mixer_ctx; + struct work_struct work; +}; + +void exynos_drm_display_ops_register(struct exynos_hdmi_display_ops + *display_ops) +{ + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (display_ops) + hdmi_display_ops = display_ops; +} +EXPORT_SYMBOL(exynos_drm_display_ops_register); + +void exynos_drm_manager_ops_register(struct exynos_hdmi_manager_ops + *manager_ops) +{ + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (manager_ops) + hdmi_manager_ops = manager_ops; +} +EXPORT_SYMBOL(exynos_drm_manager_ops_register); + +void exynos_drm_overlay_ops_register(struct exynos_hdmi_overlay_ops + *overlay_ops) +{ + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (overlay_ops) + hdmi_overlay_ops = overlay_ops; +} +EXPORT_SYMBOL(exynos_drm_overlay_ops_register); + +static bool drm_hdmi_is_connected(struct device *dev) +{ + struct drm_hdmi_context *ctx = to_context(dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_display_ops && hdmi_display_ops->is_connected) + return hdmi_display_ops->is_connected(ctx->hdmi_ctx->ctx); + + return false; +} + +static int drm_hdmi_get_edid(struct device *dev, + struct drm_connector *connector, u8 *edid, int len) +{ + struct drm_hdmi_context *ctx = to_context(dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_display_ops && hdmi_display_ops->get_edid) + return hdmi_display_ops->get_edid(ctx->hdmi_ctx->ctx, + connector, edid, len); + + return 0; +} + +static int drm_hdmi_check_timing(struct device *dev, void *timing) +{ + struct drm_hdmi_context *ctx = to_context(dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_display_ops && hdmi_display_ops->check_timing) + return hdmi_display_ops->check_timing(ctx->hdmi_ctx->ctx, + timing); + + return 0; +} + +static int drm_hdmi_power_on(struct device *dev, int mode) +{ + struct drm_hdmi_context *ctx = to_context(dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_display_ops && hdmi_display_ops->power_on) + return hdmi_display_ops->power_on(ctx->hdmi_ctx->ctx, mode); + + return 0; +} + +static struct exynos_drm_display_ops drm_hdmi_display_ops = { + .type = EXYNOS_DISPLAY_TYPE_HDMI, + .is_connected = drm_hdmi_is_connected, + .get_edid = drm_hdmi_get_edid, + .check_timing = drm_hdmi_check_timing, + .power_on = drm_hdmi_power_on, +}; + +static int drm_hdmi_enable_vblank(struct device *subdrv_dev) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + struct exynos_drm_subdrv *subdrv = &ctx->subdrv; + struct exynos_drm_manager *manager = &subdrv->manager; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_overlay_ops && hdmi_overlay_ops->enable_vblank) + return hdmi_overlay_ops->enable_vblank(ctx->mixer_ctx->ctx, + manager->pipe); + + return 0; +} + +static void drm_hdmi_disable_vblank(struct device *subdrv_dev) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_overlay_ops && hdmi_overlay_ops->disable_vblank) + return hdmi_overlay_ops->disable_vblank(ctx->mixer_ctx->ctx); +} + +static void drm_hdmi_mode_set(struct device *subdrv_dev, void *mode) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_manager_ops && hdmi_manager_ops->mode_set) + hdmi_manager_ops->mode_set(ctx->hdmi_ctx->ctx, mode); +} + +static void drm_hdmi_commit(struct device *subdrv_dev) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_manager_ops && hdmi_manager_ops->commit) + hdmi_manager_ops->commit(ctx->hdmi_ctx->ctx); +} + +static void drm_hdmi_dpms(struct device *subdrv_dev, int mode) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + switch (mode) { + case DRM_MODE_DPMS_ON: + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + if (hdmi_manager_ops && hdmi_manager_ops->disable) + hdmi_manager_ops->disable(ctx->hdmi_ctx->ctx); + break; + default: + DRM_DEBUG_KMS("unkown dps mode: %d\n", mode); + break; + } +} + +static struct exynos_drm_manager_ops drm_hdmi_manager_ops = { + .dpms = drm_hdmi_dpms, + .enable_vblank = drm_hdmi_enable_vblank, + .disable_vblank = drm_hdmi_disable_vblank, + .mode_set = drm_hdmi_mode_set, + .commit = drm_hdmi_commit, +}; + +static void drm_mixer_mode_set(struct device *subdrv_dev, + struct exynos_drm_overlay *overlay) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_overlay_ops && hdmi_overlay_ops->win_mode_set) + hdmi_overlay_ops->win_mode_set(ctx->mixer_ctx->ctx, overlay); +} + +static void drm_mixer_commit(struct device *subdrv_dev, int zpos) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_overlay_ops && hdmi_overlay_ops->win_commit) + hdmi_overlay_ops->win_commit(ctx->mixer_ctx->ctx, zpos); +} + +static void drm_mixer_disable(struct device *subdrv_dev, int zpos) +{ + struct drm_hdmi_context *ctx = to_context(subdrv_dev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + if (hdmi_overlay_ops && hdmi_overlay_ops->win_disable) + hdmi_overlay_ops->win_disable(ctx->mixer_ctx->ctx, zpos); +} + +static struct exynos_drm_overlay_ops drm_hdmi_overlay_ops = { + .mode_set = drm_mixer_mode_set, + .commit = drm_mixer_commit, + .disable = drm_mixer_disable, +}; + + +static int hdmi_subdrv_probe(struct drm_device *drm_dev, + struct device *dev) +{ + struct exynos_drm_subdrv *subdrv = to_subdrv(dev); + struct drm_hdmi_context *ctx; + struct platform_device *pdev = to_platform_device(dev); + struct exynos_drm_common_hdmi_pd *pd; + int ret; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + pd = pdev->dev.platform_data; + + if (!pd) { + DRM_DEBUG_KMS("platform data is null.\n"); + return -EFAULT; + } + + if (!pd->hdmi_dev) { + DRM_DEBUG_KMS("hdmi device is null.\n"); + return -EFAULT; + } + + if (!pd->mixer_dev) { + DRM_DEBUG_KMS("mixer device is null.\n"); + return -EFAULT; + } + + ret = platform_driver_register(&hdmi_driver); + if (ret) { + DRM_DEBUG_KMS("failed to register hdmi driver.\n"); + return ret; + } + + ret = platform_driver_register(&mixer_driver); + if (ret) { + DRM_DEBUG_KMS("failed to register mixer driver.\n"); + goto err_hdmidrv; + } + + ctx = get_ctx_from_subdrv(subdrv); + + ctx->hdmi_ctx = (struct exynos_drm_hdmi_context *) + to_context(pd->hdmi_dev); + if (!ctx->hdmi_ctx) { + DRM_DEBUG_KMS("hdmi context is null.\n"); + ret = -EFAULT; + goto err_mixerdrv; + } + + ctx->hdmi_ctx->drm_dev = drm_dev; + + ctx->mixer_ctx = (struct exynos_drm_hdmi_context *) + to_context(pd->mixer_dev); + if (!ctx->mixer_ctx) { + DRM_DEBUG_KMS("mixer context is null.\n"); + ret = -EFAULT; + goto err_mixerdrv; + } + + ctx->mixer_ctx->drm_dev = drm_dev; + + return 0; + +err_mixerdrv: + platform_driver_unregister(&mixer_driver); +err_hdmidrv: + platform_driver_unregister(&hdmi_driver); + return ret; +} + +static void hdmi_subdrv_remove(struct drm_device *drm_dev) +{ + DRM_DEBUG_KMS("%s\n", __FILE__); + + platform_driver_unregister(&hdmi_driver); + platform_driver_unregister(&mixer_driver); +} + +static void exynos_drm_hdmi_late_probe(struct work_struct *work) +{ + struct drm_hdmi_context *ctx = container_of(work, + struct drm_hdmi_context, work); + + /* + * this function calls subdrv->probe() so this must be called + * after probe context. + * + * PS. subdrv->probe() will call platform_driver_register() to probe + * hdmi and mixer driver. + */ + exynos_drm_subdrv_register(&ctx->subdrv); +} + +static int __devinit exynos_drm_hdmi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct exynos_drm_subdrv *subdrv; + struct drm_hdmi_context *ctx; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + DRM_LOG_KMS("failed to alloc common hdmi context.\n"); + return -ENOMEM; + } + + subdrv = &ctx->subdrv; + + subdrv->probe = hdmi_subdrv_probe; + subdrv->remove = hdmi_subdrv_remove; + subdrv->manager.pipe = -1; + subdrv->manager.ops = &drm_hdmi_manager_ops; + subdrv->manager.overlay_ops = &drm_hdmi_overlay_ops; + subdrv->manager.display_ops = &drm_hdmi_display_ops; + subdrv->manager.dev = dev; + + platform_set_drvdata(pdev, subdrv); + + INIT_WORK(&ctx->work, exynos_drm_hdmi_late_probe); + + schedule_work(&ctx->work); + + return 0; +} + +static int hdmi_runtime_suspend(struct device *dev) +{ + DRM_DEBUG_KMS("%s\n", __FILE__); + + return 0; +} + +static int hdmi_runtime_resume(struct device *dev) +{ + DRM_DEBUG_KMS("%s\n", __FILE__); + + return 0; +} + +static const struct dev_pm_ops hdmi_pm_ops = { + .runtime_suspend = hdmi_runtime_suspend, + .runtime_resume = hdmi_runtime_resume, +}; + +static int __devexit exynos_drm_hdmi_remove(struct platform_device *pdev) +{ + struct drm_hdmi_context *ctx = platform_get_drvdata(pdev); + + DRM_DEBUG_KMS("%s\n", __FILE__); + + exynos_drm_subdrv_unregister(&ctx->subdrv); + kfree(ctx); + + return 0; +} + +static struct platform_driver exynos_drm_common_hdmi_driver = { + .probe = exynos_drm_hdmi_probe, + .remove = __devexit_p(exynos_drm_hdmi_remove), + .driver = { + .name = "exynos-drm-hdmi", + .owner = THIS_MODULE, + .pm = &hdmi_pm_ops, + }, +}; + +static int __init exynos_drm_hdmi_init(void) +{ + int ret; + + DRM_DEBUG_KMS("%s\n", __FILE__); + + ret = platform_driver_register(&exynos_drm_common_hdmi_driver); + if (ret) { + DRM_DEBUG_KMS("failed to register hdmi common driver.\n"); + return ret; + } + + return ret; +} + +static void __exit exynos_drm_hdmi_exit(void) +{ + platform_driver_unregister(&exynos_drm_common_hdmi_driver); +} + +module_init(exynos_drm_hdmi_init); +module_exit(exynos_drm_hdmi_exit); + +MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); +MODULE_AUTHOR("Seung-Woo Kim, <sw0312.kim@samsung.com>"); +MODULE_DESCRIPTION("Samsung SoC DRM HDMI Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/exynos/exynos_drm_hdmi.h b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h new file mode 100644 index 000000000000..3c29f790ee45 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_hdmi.h @@ -0,0 +1,73 @@ +/* exynos_drm_hdmi.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Authoer: Inki Dae <inki.dae@samsung.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _EXYNOS_DRM_HDMI_H_ +#define _EXYNOS_DRM_HDMI_H_ + +/* + * exynos hdmi common context structure. + * + * @drm_dev: pointer to drm_device. + * @ctx: pointer to the context of specific device driver. + * this context should be hdmi_context or mixer_context. + */ +struct exynos_drm_hdmi_context { + struct drm_device *drm_dev; + void *ctx; +}; + +struct exynos_hdmi_display_ops { + bool (*is_connected)(void *ctx); + int (*get_edid)(void *ctx, struct drm_connector *connector, + u8 *edid, int len); + int (*check_timing)(void *ctx, void *timing); + int (*power_on)(void *ctx, int mode); +}; + +struct exynos_hdmi_manager_ops { + void (*mode_set)(void *ctx, void *mode); + void (*commit)(void *ctx); + void (*disable)(void *ctx); +}; + +struct exynos_hdmi_overlay_ops { + int (*enable_vblank)(void *ctx, int pipe); + void (*disable_vblank)(void *ctx); + void (*win_mode_set)(void *ctx, struct exynos_drm_overlay *overlay); + void (*win_commit)(void *ctx, int zpos); + void (*win_disable)(void *ctx, int zpos); +}; + +extern struct platform_driver hdmi_driver; +extern struct platform_driver mixer_driver; + +void exynos_drm_display_ops_register(struct exynos_hdmi_display_ops + *display_ops); +void exynos_drm_manager_ops_register(struct exynos_hdmi_manager_ops + *manager_ops); +void exynos_drm_overlay_ops_register(struct exynos_hdmi_overlay_ops + *overlay_ops); + +#endif diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.c b/drivers/gpu/drm/exynos/exynos_drm_plane.c new file mode 100644 index 000000000000..bdcf770aa22e --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_plane.c @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Authors: Joonyoung Shim <jy0922.shim@samsung.com> + * + * 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. + * + */ + +#include "drmP.h" + +#include "exynos_drm.h" +#include "exynos_drm_crtc.h" +#include "exynos_drm_drv.h" +#include "exynos_drm_encoder.h" + +struct exynos_plane { + struct drm_plane base; + struct exynos_drm_overlay overlay; + bool enabled; +}; + +static int +exynos_update_plane(struct drm_plane *plane, 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) +{ + struct exynos_plane *exynos_plane = + container_of(plane, struct exynos_plane, base); + struct exynos_drm_overlay *overlay = &exynos_plane->overlay; + struct exynos_drm_crtc_pos pos; + unsigned int x = src_x >> 16; + unsigned int y = src_y >> 16; + int ret; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + memset(&pos, 0, sizeof(struct exynos_drm_crtc_pos)); + pos.crtc_x = crtc_x; + pos.crtc_y = crtc_y; + pos.crtc_w = crtc_w; + pos.crtc_h = crtc_h; + + pos.fb_x = x; + pos.fb_y = y; + + /* TODO: scale feature */ + ret = exynos_drm_overlay_update(overlay, fb, &crtc->mode, &pos); + if (ret < 0) + return ret; + + exynos_drm_fn_encoder(crtc, overlay, + exynos_drm_encoder_crtc_mode_set); + exynos_drm_fn_encoder(crtc, &overlay->zpos, + exynos_drm_encoder_crtc_plane_commit); + + exynos_plane->enabled = true; + + return 0; +} + +static int exynos_disable_plane(struct drm_plane *plane) +{ + struct exynos_plane *exynos_plane = + container_of(plane, struct exynos_plane, base); + struct exynos_drm_overlay *overlay = &exynos_plane->overlay; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + if (!exynos_plane->enabled) + return 0; + + exynos_drm_fn_encoder(plane->crtc, &overlay->zpos, + exynos_drm_encoder_crtc_disable); + + exynos_plane->enabled = false; + exynos_plane->overlay.zpos = DEFAULT_ZPOS; + + return 0; +} + +static void exynos_plane_destroy(struct drm_plane *plane) +{ + struct exynos_plane *exynos_plane = + container_of(plane, struct exynos_plane, base); + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + exynos_disable_plane(plane); + drm_plane_cleanup(plane); + kfree(exynos_plane); +} + +static struct drm_plane_funcs exynos_plane_funcs = { + .update_plane = exynos_update_plane, + .disable_plane = exynos_disable_plane, + .destroy = exynos_plane_destroy, +}; + +int exynos_plane_init(struct drm_device *dev, unsigned int nr) +{ + struct exynos_plane *exynos_plane; + uint32_t possible_crtcs; + + exynos_plane = kzalloc(sizeof(struct exynos_plane), GFP_KERNEL); + if (!exynos_plane) + return -ENOMEM; + + /* all CRTCs are available */ + possible_crtcs = (1 << MAX_CRTC) - 1; + + exynos_plane->overlay.zpos = DEFAULT_ZPOS; + + /* TODO: format */ + return drm_plane_init(dev, &exynos_plane->base, possible_crtcs, + &exynos_plane_funcs, NULL, 0, false); +} + +int exynos_plane_set_zpos_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_exynos_plane_set_zpos *zpos_req = data; + struct drm_mode_object *obj; + struct drm_plane *plane; + struct exynos_plane *exynos_plane; + int ret = 0; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + if (!drm_core_check_feature(dev, DRIVER_MODESET)) + return -EINVAL; + + if (zpos_req->zpos < 0 || zpos_req->zpos >= MAX_PLANE) { + if (zpos_req->zpos != DEFAULT_ZPOS) { + DRM_ERROR("zpos not within limits\n"); + return -EINVAL; + } + } + + mutex_lock(&dev->mode_config.mutex); + + obj = drm_mode_object_find(dev, zpos_req->plane_id, + DRM_MODE_OBJECT_PLANE); + if (!obj) { + DRM_DEBUG_KMS("Unknown plane ID %d\n", + zpos_req->plane_id); + ret = -EINVAL; + goto out; + } + + plane = obj_to_plane(obj); + exynos_plane = container_of(plane, struct exynos_plane, base); + + exynos_plane->overlay.zpos = zpos_req->zpos; + +out: + mutex_unlock(&dev->mode_config.mutex); + return ret; +} diff --git a/drivers/gpu/drm/exynos/exynos_drm_plane.h b/drivers/gpu/drm/exynos/exynos_drm_plane.h new file mode 100644 index 000000000000..16b71f8217e7 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_drm_plane.h @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Authors: Joonyoung Shim <jy0922.shim@samsung.com> + * + * 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. + * + */ + +int exynos_plane_init(struct drm_device *dev, unsigned int nr); +int exynos_plane_set_zpos_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.c b/drivers/gpu/drm/exynos/exynos_hdmi.c new file mode 100644 index 000000000000..f48f7ce92f5f --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_hdmi.c @@ -0,0 +1,1176 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Authors: + * Seung-Woo Kim <sw0312.kim@samsung.com> + * Inki Dae <inki.dae@samsung.com> + * Joonyoung Shim <jy0922.shim@samsung.com> + * + * Based on drivers/media/video/s5p-tv/hdmi_drv.c + * + * 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. + * + */ + +#include "drmP.h" +#include "drm_edid.h" +#include "drm_crtc_helper.h" + +#include "regs-hdmi.h" + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> + +#include <drm/exynos_drm.h> + +#include "exynos_drm_drv.h" +#include "exynos_drm_hdmi.h" + +#include "exynos_hdmi.h" + +#define HDMI_OVERLAY_NUMBER 3 +#define get_hdmi_context(dev) platform_get_drvdata(to_platform_device(dev)) + +static const u8 hdmiphy_conf27[32] = { + 0x01, 0x05, 0x00, 0xD8, 0x10, 0x1C, 0x30, 0x40, + 0x6B, 0x10, 0x02, 0x51, 0xDF, 0xF2, 0x54, 0x87, + 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, + 0x22, 0x40, 0xE3, 0x26, 0x00, 0x00, 0x00, 0x00, +}; + +static const u8 hdmiphy_conf27_027[32] = { + 0x01, 0x05, 0x00, 0xD4, 0x10, 0x9C, 0x09, 0x64, + 0x6B, 0x10, 0x02, 0x51, 0xDF, 0xF2, 0x54, 0x87, + 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, + 0x22, 0x40, 0xE3, 0x26, 0x00, 0x00, 0x00, 0x00, +}; + +static const u8 hdmiphy_conf74_175[32] = { + 0x01, 0x05, 0x00, 0xD8, 0x10, 0x9C, 0xef, 0x5B, + 0x6D, 0x10, 0x01, 0x51, 0xef, 0xF3, 0x54, 0xb9, + 0x84, 0x00, 0x30, 0x38, 0x00, 0x08, 0x10, 0xE0, + 0x22, 0x40, 0xa5, 0x26, 0x01, 0x00, 0x00, 0x00, +}; + +static const u8 hdmiphy_conf74_25[32] = { + 0x01, 0x05, 0x00, 0xd8, 0x10, 0x9c, 0xf8, 0x40, + 0x6a, 0x10, 0x01, 0x51, 0xff, 0xf1, 0x54, 0xba, + 0x84, 0x00, 0x10, 0x38, 0x00, 0x08, 0x10, 0xe0, + 0x22, 0x40, 0xa4, 0x26, 0x01, 0x00, 0x00, 0x00, +}; + +static const u8 hdmiphy_conf148_5[32] = { + 0x01, 0x05, 0x00, 0xD8, 0x10, 0x9C, 0xf8, 0x40, + 0x6A, 0x18, 0x00, 0x51, 0xff, 0xF1, 0x54, 0xba, + 0x84, 0x00, 0x10, 0x38, 0x00, 0x08, 0x10, 0xE0, + 0x22, 0x40, 0xa4, 0x26, 0x02, 0x00, 0x00, 0x00, +}; + +struct hdmi_tg_regs { + u8 cmd; + u8 h_fsz_l; + u8 h_fsz_h; + u8 hact_st_l; + u8 hact_st_h; + u8 hact_sz_l; + u8 hact_sz_h; + u8 v_fsz_l; + u8 v_fsz_h; + u8 vsync_l; + u8 vsync_h; + u8 vsync2_l; + u8 vsync2_h; + u8 vact_st_l; + u8 vact_st_h; + u8 vact_sz_l; + u8 vact_sz_h; + u8 field_chg_l; + u8 field_chg_h; + u8 vact_st2_l; + u8 vact_st2_h; + u8 vsync_top_hdmi_l; + u8 vsync_top_hdmi_h; + u8 vsync_bot_hdmi_l; + u8 vsync_bot_hdmi_h; + u8 field_top_hdmi_l; + u8 field_top_hdmi_h; + u8 field_bot_hdmi_l; + u8 field_bot_hdmi_h; +}; + +struct hdmi_core_regs { + u8 h_blank[2]; + u8 v_blank[3]; + u8 h_v_line[3]; + u8 vsync_pol[1]; + u8 int_pro_mode[1]; + u8 v_blank_f[3]; + u8 h_sync_gen[3]; + u8 v_sync_gen1[3]; + u8 v_sync_gen2[3]; + u8 v_sync_gen3[3]; +}; + +struct hdmi_preset_conf { + struct hdmi_core_regs core; + struct hdmi_tg_regs tg; +}; + +static const struct hdmi_preset_conf hdmi_conf_480p = { + .core = { + .h_blank = {0x8a, 0x00}, + .v_blank = {0x0d, 0x6a, 0x01}, + .h_v_line = {0x0d, 0xa2, 0x35}, + .vsync_pol = {0x01}, + .int_pro_mode = {0x00}, + .v_blank_f = {0x00, 0x00, 0x00}, + .h_sync_gen = {0x0e, 0x30, 0x11}, + .v_sync_gen1 = {0x0f, 0x90, 0x00}, + /* other don't care */ + }, + .tg = { + 0x00, /* cmd */ + 0x5a, 0x03, /* h_fsz */ + 0x8a, 0x00, 0xd0, 0x02, /* hact */ + 0x0d, 0x02, /* v_fsz */ + 0x01, 0x00, 0x33, 0x02, /* vsync */ + 0x2d, 0x00, 0xe0, 0x01, /* vact */ + 0x33, 0x02, /* field_chg */ + 0x49, 0x02, /* vact_st2 */ + 0x01, 0x00, 0x33, 0x02, /* vsync top/bot */ + 0x01, 0x00, 0x33, 0x02, /* field top/bot */ + }, +}; + +static const struct hdmi_preset_conf hdmi_conf_720p60 = { + .core = { + .h_blank = {0x72, 0x01}, + .v_blank = {0xee, 0xf2, 0x00}, + .h_v_line = {0xee, 0x22, 0x67}, + .vsync_pol = {0x00}, + .int_pro_mode = {0x00}, + .v_blank_f = {0x00, 0x00, 0x00}, /* don't care */ + .h_sync_gen = {0x6c, 0x50, 0x02}, + .v_sync_gen1 = {0x0a, 0x50, 0x00}, + .v_sync_gen2 = {0x01, 0x10, 0x00}, + .v_sync_gen3 = {0x01, 0x10, 0x00}, + /* other don't care */ + }, + .tg = { + 0x00, /* cmd */ + 0x72, 0x06, /* h_fsz */ + 0x71, 0x01, 0x01, 0x05, /* hact */ + 0xee, 0x02, /* v_fsz */ + 0x01, 0x00, 0x33, 0x02, /* vsync */ + 0x1e, 0x00, 0xd0, 0x02, /* vact */ + 0x33, 0x02, /* field_chg */ + 0x49, 0x02, /* vact_st2 */ + 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ + 0x01, 0x00, 0x33, 0x02, /* field top/bot */ + }, +}; + +static const struct hdmi_preset_conf hdmi_conf_1080i50 = { + .core = { + .h_blank = {0xd0, 0x02}, + .v_blank = {0x32, 0xB2, 0x00}, + .h_v_line = {0x65, 0x04, 0xa5}, + .vsync_pol = {0x00}, + .int_pro_mode = {0x01}, + .v_blank_f = {0x49, 0x2A, 0x23}, + .h_sync_gen = {0x0E, 0xEA, 0x08}, + .v_sync_gen1 = {0x07, 0x20, 0x00}, + .v_sync_gen2 = {0x39, 0x42, 0x23}, + .v_sync_gen3 = {0x38, 0x87, 0x73}, + /* other don't care */ + }, + .tg = { + 0x00, /* cmd */ + 0x50, 0x0A, /* h_fsz */ + 0xCF, 0x02, 0x81, 0x07, /* hact */ + 0x65, 0x04, /* v_fsz */ + 0x01, 0x00, 0x33, 0x02, /* vsync */ + 0x16, 0x00, 0x1c, 0x02, /* vact */ + 0x33, 0x02, /* field_chg */ + 0x49, 0x02, /* vact_st2 */ + 0x01, 0x00, 0x33, 0x02, /* vsync top/bot */ + 0x01, 0x00, 0x33, 0x02, /* field top/bot */ + }, +}; + +static const struct hdmi_preset_conf hdmi_conf_1080p50 = { + .core = { + .h_blank = {0xd0, 0x02}, + .v_blank = {0x65, 0x6c, 0x01}, + .h_v_line = {0x65, 0x04, 0xa5}, + .vsync_pol = {0x00}, + .int_pro_mode = {0x00}, + .v_blank_f = {0x00, 0x00, 0x00}, /* don't care */ + .h_sync_gen = {0x0e, 0xea, 0x08}, + .v_sync_gen1 = {0x09, 0x40, 0x00}, + .v_sync_gen2 = {0x01, 0x10, 0x00}, + .v_sync_gen3 = {0x01, 0x10, 0x00}, + /* other don't care */ + }, + .tg = { + 0x00, /* cmd */ + 0x50, 0x0A, /* h_fsz */ + 0xCF, 0x02, 0x81, 0x07, /* hact */ + 0x65, 0x04, /* v_fsz */ + 0x01, 0x00, 0x33, 0x02, /* vsync */ + 0x2d, 0x00, 0x38, 0x04, /* vact */ + 0x33, 0x02, /* field_chg */ + 0x48, 0x02, /* vact_st2 */ + 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ + 0x01, 0x00, 0x33, 0x02, /* field top/bot */ + }, +}; + +static const struct hdmi_preset_conf hdmi_conf_1080i60 = { + .core = { + .h_blank = {0x18, 0x01}, + .v_blank = {0x32, 0xB2, 0x00}, + .h_v_line = {0x65, 0x84, 0x89}, + .vsync_pol = {0x00}, + .int_pro_mode = {0x01}, + .v_blank_f = {0x49, 0x2A, 0x23}, + .h_sync_gen = {0x56, 0x08, 0x02}, + .v_sync_gen1 = {0x07, 0x20, 0x00}, + .v_sync_gen2 = {0x39, 0x42, 0x23}, + .v_sync_gen3 = {0xa4, 0x44, 0x4a}, + /* other don't care */ + }, + .tg = { + 0x00, /* cmd */ + 0x98, 0x08, /* h_fsz */ + 0x17, 0x01, 0x81, 0x07, /* hact */ + 0x65, 0x04, /* v_fsz */ + 0x01, 0x00, 0x33, 0x02, /* vsync */ + 0x16, 0x00, 0x1c, 0x02, /* vact */ + 0x33, 0x02, /* field_chg */ + 0x49, 0x02, /* vact_st2 */ + 0x01, 0x00, 0x33, 0x02, /* vsync top/bot */ + 0x01, 0x00, 0x33, 0x02, /* field top/bot */ + }, +}; + +static const struct hdmi_preset_conf hdmi_conf_1080p60 = { + .core = { + .h_blank = {0x18, 0x01}, + .v_blank = {0x65, 0x6c, 0x01}, + .h_v_line = {0x65, 0x84, 0x89}, + .vsync_pol = {0x00}, + .int_pro_mode = {0x00}, + .v_blank_f = {0x00, 0x00, 0x00}, /* don't care */ + .h_sync_gen = {0x56, 0x08, 0x02}, + .v_sync_gen1 = {0x09, 0x40, 0x00}, + .v_sync_gen2 = {0x01, 0x10, 0x00}, + .v_sync_gen3 = {0x01, 0x10, 0x00}, + /* other don't care */ + }, + .tg = { + 0x00, /* cmd */ + 0x98, 0x08, /* h_fsz */ + 0x17, 0x01, 0x81, 0x07, /* hact */ + 0x65, 0x04, /* v_fsz */ + 0x01, 0x00, 0x33, 0x02, /* vsync */ + 0x2d, 0x00, 0x38, 0x04, /* vact */ + 0x33, 0x02, /* field_chg */ + 0x48, 0x02, /* vact_st2 */ + 0x01, 0x00, 0x01, 0x00, /* vsync top/bot */ + 0x01, 0x00, 0x33, 0x02, /* field top/bot */ + }, +}; + +static const struct hdmi_conf hdmi_confs[] = { + { 1280, 720, 60, false, hdmiphy_conf74_25, &hdmi_conf_720p60 }, + { 1280, 720, 50, false, hdmiphy_conf74_25, &hdmi_conf_720p60 }, + { 720, 480, 60, false, hdmiphy_conf27_027, &hdmi_conf_480p }, + { 1920, 1080, 50, true, hdmiphy_conf74_25, &hdmi_conf_1080i50 }, + { 1920, 1080, 50, false, hdmiphy_conf148_5, &hdmi_conf_1080p50 }, + { 1920, 1080, 60, true, hdmiphy_conf74_25, &hdmi_conf_1080i60 }, + { 1920, 1080, 60, false, hdmiphy_conf148_5, &hdmi_conf_1080p60 }, +}; + + +static inline u32 hdmi_reg_read(struct hdmi_context *hdata, u32 reg_id) +{ + return readl(hdata->regs + reg_id); +} + +static inline void hdmi_reg_writeb(struct hdmi_context *hdata, + u32 reg_id, u8 value) +{ + writeb(value, hdata->regs + reg_id); +} + +static inline void hdmi_reg_writemask(struct hdmi_context *hdata, + u32 reg_id, u32 value, u32 mask) +{ + u32 old = readl(hdata->regs + reg_id); + value = (value & mask) | (old & ~mask); + writel(value, hdata->regs + reg_id); +} + +static void hdmi_regs_dump(struct hdmi_context *hdata, char *prefix) +{ +#define DUMPREG(reg_id) \ + DRM_DEBUG_KMS("%s:" #reg_id " = %08x\n", prefix, \ + readl(hdata->regs + reg_id)) + DRM_DEBUG_KMS("%s: ---- CONTROL REGISTERS ----\n", prefix); + DUMPREG(HDMI_INTC_FLAG); + DUMPREG(HDMI_INTC_CON); + DUMPREG(HDMI_HPD_STATUS); + DUMPREG(HDMI_PHY_RSTOUT); + DUMPREG(HDMI_PHY_VPLL); + DUMPREG(HDMI_PHY_CMU); + DUMPREG(HDMI_CORE_RSTOUT); + + DRM_DEBUG_KMS("%s: ---- CORE REGISTERS ----\n", prefix); + DUMPREG(HDMI_CON_0); + DUMPREG(HDMI_CON_1); + DUMPREG(HDMI_CON_2); + DUMPREG(HDMI_SYS_STATUS); + DUMPREG(HDMI_PHY_STATUS); + DUMPREG(HDMI_STATUS_EN); + DUMPREG(HDMI_HPD); + DUMPREG(HDMI_MODE_SEL); + DUMPREG(HDMI_HPD_GEN); + DUMPREG(HDMI_DC_CONTROL); + DUMPREG(HDMI_VIDEO_PATTERN_GEN); + + DRM_DEBUG_KMS("%s: ---- CORE SYNC REGISTERS ----\n", prefix); + DUMPREG(HDMI_H_BLANK_0); + DUMPREG(HDMI_H_BLANK_1); + DUMPREG(HDMI_V_BLANK_0); + DUMPREG(HDMI_V_BLANK_1); + DUMPREG(HDMI_V_BLANK_2); + DUMPREG(HDMI_H_V_LINE_0); + DUMPREG(HDMI_H_V_LINE_1); + DUMPREG(HDMI_H_V_LINE_2); + DUMPREG(HDMI_VSYNC_POL); + DUMPREG(HDMI_INT_PRO_MODE); + DUMPREG(HDMI_V_BLANK_F_0); + DUMPREG(HDMI_V_BLANK_F_1); + DUMPREG(HDMI_V_BLANK_F_2); + DUMPREG(HDMI_H_SYNC_GEN_0); + DUMPREG(HDMI_H_SYNC_GEN_1); + DUMPREG(HDMI_H_SYNC_GEN_2); + DUMPREG(HDMI_V_SYNC_GEN_1_0); + DUMPREG(HDMI_V_SYNC_GEN_1_1); + DUMPREG(HDMI_V_SYNC_GEN_1_2); + DUMPREG(HDMI_V_SYNC_GEN_2_0); + DUMPREG(HDMI_V_SYNC_GEN_2_1); + DUMPREG(HDMI_V_SYNC_GEN_2_2); + DUMPREG(HDMI_V_SYNC_GEN_3_0); + DUMPREG(HDMI_V_SYNC_GEN_3_1); + DUMPREG(HDMI_V_SYNC_GEN_3_2); + + DRM_DEBUG_KMS("%s: ---- TG REGISTERS ----\n", prefix); + DUMPREG(HDMI_TG_CMD); + DUMPREG(HDMI_TG_H_FSZ_L); + DUMPREG(HDMI_TG_H_FSZ_H); + DUMPREG(HDMI_TG_HACT_ST_L); + DUMPREG(HDMI_TG_HACT_ST_H); + DUMPREG(HDMI_TG_HACT_SZ_L); + DUMPREG(HDMI_TG_HACT_SZ_H); + DUMPREG(HDMI_TG_V_FSZ_L); + DUMPREG(HDMI_TG_V_FSZ_H); + DUMPREG(HDMI_TG_VSYNC_L); + DUMPREG(HDMI_TG_VSYNC_H); + DUMPREG(HDMI_TG_VSYNC2_L); + DUMPREG(HDMI_TG_VSYNC2_H); + DUMPREG(HDMI_TG_VACT_ST_L); + DUMPREG(HDMI_TG_VACT_ST_H); + DUMPREG(HDMI_TG_VACT_SZ_L); + DUMPREG(HDMI_TG_VACT_SZ_H); + DUMPREG(HDMI_TG_FIELD_CHG_L); + DUMPREG(HDMI_TG_FIELD_CHG_H); + DUMPREG(HDMI_TG_VACT_ST2_L); + DUMPREG(HDMI_TG_VACT_ST2_H); + DUMPREG(HDMI_TG_VSYNC_TOP_HDMI_L); + DUMPREG(HDMI_TG_VSYNC_TOP_HDMI_H); + DUMPREG(HDMI_TG_VSYNC_BOT_HDMI_L); + DUMPREG(HDMI_TG_VSYNC_BOT_HDMI_H); + DUMPREG(HDMI_TG_FIELD_TOP_HDMI_L); + DUMPREG(HDMI_TG_FIELD_TOP_HDMI_H); + DUMPREG(HDMI_TG_FIELD_BOT_HDMI_L); + DUMPREG(HDMI_TG_FIELD_BOT_HDMI_H); +#undef DUMPREG +} + +static int hdmi_conf_index(struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(hdmi_confs); ++i) + if (hdmi_confs[i].width == mode->hdisplay && + hdmi_confs[i].height == mode->vdisplay && + hdmi_confs[i].vrefresh == mode->vrefresh && + hdmi_confs[i].interlace == + ((mode->flags & DRM_MODE_FLAG_INTERLACE) ? + true : false)) + return i; + + return -1; +} + +static bool hdmi_is_connected(void *ctx) +{ + struct hdmi_context *hdata = (struct hdmi_context *)ctx; + u32 val = hdmi_reg_read(hdata, HDMI_HPD_STATUS); + + if (val) + return true; + + return false; +} + +static int hdmi_get_edid(void *ctx, struct drm_connector *connector, + u8 *edid, int len) +{ + struct edid *raw_edid; + struct hdmi_context *hdata = (struct hdmi_context *)ctx; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + if (!hdata->ddc_port) + return -ENODEV; + + raw_edid = drm_get_edid(connector, hdata->ddc_port->adapter); + if (raw_edid) { + memcpy(edid, raw_edid, min((1 + raw_edid->extensions) + * EDID_LENGTH, len)); + DRM_DEBUG_KMS("width[%d] x height[%d]\n", + raw_edid->width_cm, raw_edid->height_cm); + } else { + return -ENODEV; + } + + return 0; +} + +static int hdmi_check_timing(void *ctx, void *timing) +{ + struct fb_videomode *check_timing = timing; + int i; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + DRM_DEBUG_KMS("[%d]x[%d] [%d]Hz [%x]\n", check_timing->xres, + check_timing->yres, check_timing->refresh, + check_timing->vmode); + + for (i = 0; i < ARRAY_SIZE(hdmi_confs); ++i) + if (hdmi_confs[i].width == check_timing->xres && + hdmi_confs[i].height == check_timing->yres && + hdmi_confs[i].vrefresh == check_timing->refresh && + hdmi_confs[i].interlace == + ((check_timing->vmode & FB_VMODE_INTERLACED) ? + true : false)) + return 0; + + return -EINVAL; +} + +static int hdmi_display_power_on(void *ctx, int mode) +{ + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + switch (mode) { + case DRM_MODE_DPMS_ON: + DRM_DEBUG_KMS("hdmi [on]\n"); + break; + case DRM_MODE_DPMS_STANDBY: + break; + case DRM_MODE_DPMS_SUSPEND: + break; + case DRM_MODE_DPMS_OFF: + DRM_DEBUG_KMS("hdmi [off]\n"); + break; + default: + break; + } + + return 0; +} + +static struct exynos_hdmi_display_ops display_ops = { + .is_connected = hdmi_is_connected, + .get_edid = hdmi_get_edid, + .check_timing = hdmi_check_timing, + .power_on = hdmi_display_power_on, +}; + +static void hdmi_conf_reset(struct hdmi_context *hdata) +{ + /* disable hpd handle for drm */ + hdata->hpd_handle = false; + + /* resetting HDMI core */ + hdmi_reg_writemask(hdata, HDMI_CORE_RSTOUT, 0, HDMI_CORE_SW_RSTOUT); + mdelay(10); + hdmi_reg_writemask(hdata, HDMI_CORE_RSTOUT, ~0, HDMI_CORE_SW_RSTOUT); + mdelay(10); + + /* enable hpd handle for drm */ + hdata->hpd_handle = true; +} + +static void hdmi_conf_init(struct hdmi_context *hdata) +{ + /* disable hpd handle for drm */ + hdata->hpd_handle = false; + + /* enable HPD interrupts */ + hdmi_reg_writemask(hdata, HDMI_INTC_CON, 0, HDMI_INTC_EN_GLOBAL | + HDMI_INTC_EN_HPD_PLUG | HDMI_INTC_EN_HPD_UNPLUG); + mdelay(10); + hdmi_reg_writemask(hdata, HDMI_INTC_CON, ~0, HDMI_INTC_EN_GLOBAL | + HDMI_INTC_EN_HPD_PLUG | HDMI_INTC_EN_HPD_UNPLUG); + + /* choose HDMI mode */ + hdmi_reg_writemask(hdata, HDMI_MODE_SEL, + HDMI_MODE_HDMI_EN, HDMI_MODE_MASK); + /* disable bluescreen */ + hdmi_reg_writemask(hdata, HDMI_CON_0, 0, HDMI_BLUE_SCR_EN); + /* choose bluescreen (fecal) color */ + hdmi_reg_writeb(hdata, HDMI_BLUE_SCREEN_0, 0x12); + hdmi_reg_writeb(hdata, HDMI_BLUE_SCREEN_1, 0x34); + hdmi_reg_writeb(hdata, HDMI_BLUE_SCREEN_2, 0x56); + /* enable AVI packet every vsync, fixes purple line problem */ + hdmi_reg_writeb(hdata, HDMI_AVI_CON, 0x02); + /* force RGB, look to CEA-861-D, table 7 for more detail */ + hdmi_reg_writeb(hdata, HDMI_AVI_BYTE(0), 0 << 5); + hdmi_reg_writemask(hdata, HDMI_CON_1, 0x10 << 5, 0x11 << 5); + + hdmi_reg_writeb(hdata, HDMI_SPD_CON, 0x02); + hdmi_reg_writeb(hdata, HDMI_AUI_CON, 0x02); + hdmi_reg_writeb(hdata, HDMI_ACR_CON, 0x04); + + /* enable hpd handle for drm */ + hdata->hpd_handle = true; +} + +static void hdmi_timing_apply(struct hdmi_context *hdata, + const struct hdmi_preset_conf *conf) +{ + const struct hdmi_core_regs *core = &conf->core; + const struct hdmi_tg_regs *tg = &conf->tg; + int tries; + + /* setting core registers */ + hdmi_reg_writeb(hdata, HDMI_H_BLANK_0, core->h_blank[0]); + hdmi_reg_writeb(hdata, HDMI_H_BLANK_1, core->h_blank[1]); + hdmi_reg_writeb(hdata, HDMI_V_BLANK_0, core->v_blank[0]); + hdmi_reg_writeb(hdata, HDMI_V_BLANK_1, core->v_blank[1]); + hdmi_reg_writeb(hdata, HDMI_V_BLANK_2, core->v_blank[2]); + hdmi_reg_writeb(hdata, HDMI_H_V_LINE_0, core->h_v_line[0]); + hdmi_reg_writeb(hdata, HDMI_H_V_LINE_1, core->h_v_line[1]); + hdmi_reg_writeb(hdata, HDMI_H_V_LINE_2, core->h_v_line[2]); + hdmi_reg_writeb(hdata, HDMI_VSYNC_POL, core->vsync_pol[0]); + hdmi_reg_writeb(hdata, HDMI_INT_PRO_MODE, core->int_pro_mode[0]); + hdmi_reg_writeb(hdata, HDMI_V_BLANK_F_0, core->v_blank_f[0]); + hdmi_reg_writeb(hdata, HDMI_V_BLANK_F_1, core->v_blank_f[1]); + hdmi_reg_writeb(hdata, HDMI_V_BLANK_F_2, core->v_blank_f[2]); + hdmi_reg_writeb(hdata, HDMI_H_SYNC_GEN_0, core->h_sync_gen[0]); + hdmi_reg_writeb(hdata, HDMI_H_SYNC_GEN_1, core->h_sync_gen[1]); + hdmi_reg_writeb(hdata, HDMI_H_SYNC_GEN_2, core->h_sync_gen[2]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_1_0, core->v_sync_gen1[0]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_1_1, core->v_sync_gen1[1]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_1_2, core->v_sync_gen1[2]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_2_0, core->v_sync_gen2[0]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_2_1, core->v_sync_gen2[1]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_2_2, core->v_sync_gen2[2]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_3_0, core->v_sync_gen3[0]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_3_1, core->v_sync_gen3[1]); + hdmi_reg_writeb(hdata, HDMI_V_SYNC_GEN_3_2, core->v_sync_gen3[2]); + /* Timing generator registers */ + hdmi_reg_writeb(hdata, HDMI_TG_H_FSZ_L, tg->h_fsz_l); + hdmi_reg_writeb(hdata, HDMI_TG_H_FSZ_H, tg->h_fsz_h); + hdmi_reg_writeb(hdata, HDMI_TG_HACT_ST_L, tg->hact_st_l); + hdmi_reg_writeb(hdata, HDMI_TG_HACT_ST_H, tg->hact_st_h); + hdmi_reg_writeb(hdata, HDMI_TG_HACT_SZ_L, tg->hact_sz_l); + hdmi_reg_writeb(hdata, HDMI_TG_HACT_SZ_H, tg->hact_sz_h); + hdmi_reg_writeb(hdata, HDMI_TG_V_FSZ_L, tg->v_fsz_l); + hdmi_reg_writeb(hdata, HDMI_TG_V_FSZ_H, tg->v_fsz_h); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_L, tg->vsync_l); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_H, tg->vsync_h); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC2_L, tg->vsync2_l); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC2_H, tg->vsync2_h); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST_L, tg->vact_st_l); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST_H, tg->vact_st_h); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_SZ_L, tg->vact_sz_l); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_SZ_H, tg->vact_sz_h); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_CHG_L, tg->field_chg_l); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_CHG_H, tg->field_chg_h); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST2_L, tg->vact_st2_l); + hdmi_reg_writeb(hdata, HDMI_TG_VACT_ST2_H, tg->vact_st2_h); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_TOP_HDMI_L, tg->vsync_top_hdmi_l); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_TOP_HDMI_H, tg->vsync_top_hdmi_h); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_BOT_HDMI_L, tg->vsync_bot_hdmi_l); + hdmi_reg_writeb(hdata, HDMI_TG_VSYNC_BOT_HDMI_H, tg->vsync_bot_hdmi_h); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_TOP_HDMI_L, tg->field_top_hdmi_l); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_TOP_HDMI_H, tg->field_top_hdmi_h); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_BOT_HDMI_L, tg->field_bot_hdmi_l); + hdmi_reg_writeb(hdata, HDMI_TG_FIELD_BOT_HDMI_H, tg->field_bot_hdmi_h); + + /* waiting for HDMIPHY's PLL to get to steady state */ + for (tries = 100; tries; --tries) { + u32 val = hdmi_reg_read(hdata, HDMI_PHY_STATUS); + if (val & HDMI_PHY_STATUS_READY) + break; + mdelay(1); + } + /* steady state not achieved */ + if (tries == 0) { + DRM_ERROR("hdmiphy's pll could not reach steady state.\n"); + hdmi_regs_dump(hdata, "timing apply"); + } + + clk_disable(hdata->res.sclk_hdmi); + clk_set_parent(hdata->res.sclk_hdmi, hdata->res.sclk_hdmiphy); + clk_enable(hdata->res.sclk_hdmi); + + /* enable HDMI and timing generator */ + hdmi_reg_writemask(hdata, HDMI_CON_0, ~0, HDMI_EN); + if (core->int_pro_mode[0]) + hdmi_reg_writemask(hdata, HDMI_TG_CMD, ~0, HDMI_TG_EN | + HDMI_FIELD_EN); + else + hdmi_reg_writemask(hdata, HDMI_TG_CMD, ~0, HDMI_TG_EN); +} + +static void hdmiphy_conf_reset(struct hdmi_context *hdata) +{ + u8 buffer[2]; + + clk_disable(hdata->res.sclk_hdmi); + clk_set_parent(hdata->res.sclk_hdmi, hdata->res.sclk_pixel); + clk_enable(hdata->res.sclk_hdmi); + + /* operation mode */ + buffer[0] = 0x1f; + buffer[1] = 0x00; + + if (hdata->hdmiphy_port) + i2c_master_send(hdata->hdmiphy_port, buffer, 2); + + /* reset hdmiphy */ + hdmi_reg_writemask(hdata, HDMI_PHY_RSTOUT, ~0, HDMI_PHY_SW_RSTOUT); + mdelay(10); + hdmi_reg_writemask(hdata, HDMI_PHY_RSTOUT, 0, HDMI_PHY_SW_RSTOUT); + mdelay(10); +} + +static void hdmiphy_conf_apply(struct hdmi_context *hdata) +{ + u8 buffer[32]; + u8 operation[2]; + u8 read_buffer[32] = {0, }; + int ret; + int i; + + if (!hdata->hdmiphy_port) { + DRM_ERROR("hdmiphy is not attached\n"); + return; + } + + /* pixel clock */ + memcpy(buffer, hdmi_confs[hdata->cur_conf].hdmiphy_data, 32); + ret = i2c_master_send(hdata->hdmiphy_port, buffer, 32); + if (ret != 32) { + DRM_ERROR("failed to configure HDMIPHY via I2C\n"); + return; + } + + mdelay(10); + + /* operation mode */ + operation[0] = 0x1f; + operation[1] = 0x80; + + ret = i2c_master_send(hdata->hdmiphy_port, operation, 2); + if (ret != 2) { + DRM_ERROR("failed to enable hdmiphy\n"); + return; + } + + ret = i2c_master_recv(hdata->hdmiphy_port, read_buffer, 32); + if (ret < 0) { + DRM_ERROR("failed to read hdmiphy config\n"); + return; + } + + for (i = 0; i < ret; i++) + DRM_DEBUG_KMS("hdmiphy[0x%02x] write[0x%02x] - " + "recv [0x%02x]\n", i, buffer[i], read_buffer[i]); +} + +static void hdmi_conf_apply(struct hdmi_context *hdata) +{ + const struct hdmi_preset_conf *conf = + hdmi_confs[hdata->cur_conf].conf; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + hdmiphy_conf_reset(hdata); + hdmiphy_conf_apply(hdata); + + hdmi_conf_reset(hdata); + hdmi_conf_init(hdata); + + /* setting core registers */ + hdmi_timing_apply(hdata, conf); + + hdmi_regs_dump(hdata, "start"); +} + +static void hdmi_mode_set(void *ctx, void *mode) +{ + struct hdmi_context *hdata = (struct hdmi_context *)ctx; + int conf_idx; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + conf_idx = hdmi_conf_index(mode); + if (conf_idx >= 0 && conf_idx < ARRAY_SIZE(hdmi_confs)) + hdata->cur_conf = conf_idx; + else + DRM_DEBUG_KMS("not supported mode\n"); +} + +static void hdmi_commit(void *ctx) +{ + struct hdmi_context *hdata = (struct hdmi_context *)ctx; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + hdmi_conf_apply(hdata); + + hdata->enabled = true; +} + +static void hdmi_disable(void *ctx) +{ + struct hdmi_context *hdata = (struct hdmi_context *)ctx; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + if (hdata->enabled) { + hdmiphy_conf_reset(hdata); + hdmi_conf_reset(hdata); + } +} + +static struct exynos_hdmi_manager_ops manager_ops = { + .mode_set = hdmi_mode_set, + .commit = hdmi_commit, + .disable = hdmi_disable, +}; + +/* + * Handle hotplug events outside the interrupt handler proper. + */ +static void hdmi_hotplug_func(struct work_struct *work) +{ + struct hdmi_context *hdata = + container_of(work, struct hdmi_context, hotplug_work); + struct exynos_drm_hdmi_context *ctx = + (struct exynos_drm_hdmi_context *)hdata->parent_ctx; + + drm_helper_hpd_irq_event(ctx->drm_dev); +} + +static irqreturn_t hdmi_irq_handler(int irq, void *arg) +{ + struct exynos_drm_hdmi_context *ctx = arg; + struct hdmi_context *hdata = (struct hdmi_context *)ctx->ctx; + u32 intc_flag; + + intc_flag = hdmi_reg_read(hdata, HDMI_INTC_FLAG); + /* clearing flags for HPD plug/unplug */ + if (intc_flag & HDMI_INTC_FLAG_HPD_UNPLUG) { + DRM_DEBUG_KMS("unplugged, handling:%d\n", hdata->hpd_handle); + hdmi_reg_writemask(hdata, HDMI_INTC_FLAG, ~0, + HDMI_INTC_FLAG_HPD_UNPLUG); + } + if (intc_flag & HDMI_INTC_FLAG_HPD_PLUG) { + DRM_DEBUG_KMS("plugged, handling:%d\n", hdata->hpd_handle); + hdmi_reg_writemask(hdata, HDMI_INTC_FLAG, ~0, + HDMI_INTC_FLAG_HPD_PLUG); + } + + if (ctx->drm_dev && hdata->hpd_handle) + queue_work(hdata->wq, &hdata->hotplug_work); + + return IRQ_HANDLED; +} + +static int __devinit hdmi_resources_init(struct hdmi_context *hdata) +{ + struct device *dev = hdata->dev; + struct hdmi_resources *res = &hdata->res; + static char *supply[] = { + "hdmi-en", + "vdd", + "vdd_osc", + "vdd_pll", + }; + int i, ret; + + DRM_DEBUG_KMS("HDMI resource init\n"); + + memset(res, 0, sizeof *res); + + /* get clocks, power */ + res->hdmi = clk_get(dev, "hdmi"); + if (IS_ERR_OR_NULL(res->hdmi)) { + DRM_ERROR("failed to get clock 'hdmi'\n"); + goto fail; + } + res->sclk_hdmi = clk_get(dev, "sclk_hdmi"); + if (IS_ERR_OR_NULL(res->sclk_hdmi)) { + DRM_ERROR("failed to get clock 'sclk_hdmi'\n"); + goto fail; + } + res->sclk_pixel = clk_get(dev, "sclk_pixel"); + if (IS_ERR_OR_NULL(res->sclk_pixel)) { + DRM_ERROR("failed to get clock 'sclk_pixel'\n"); + goto fail; + } + res->sclk_hdmiphy = clk_get(dev, "sclk_hdmiphy"); + if (IS_ERR_OR_NULL(res->sclk_hdmiphy)) { + DRM_ERROR("failed to get clock 'sclk_hdmiphy'\n"); + goto fail; + } + res->hdmiphy = clk_get(dev, "hdmiphy"); + if (IS_ERR_OR_NULL(res->hdmiphy)) { + DRM_ERROR("failed to get clock 'hdmiphy'\n"); + goto fail; + } + + clk_set_parent(res->sclk_hdmi, res->sclk_pixel); + + res->regul_bulk = kzalloc(ARRAY_SIZE(supply) * + sizeof res->regul_bulk[0], GFP_KERNEL); + if (!res->regul_bulk) { + DRM_ERROR("failed to get memory for regulators\n"); + goto fail; + } + for (i = 0; i < ARRAY_SIZE(supply); ++i) { + res->regul_bulk[i].supply = supply[i]; + res->regul_bulk[i].consumer = NULL; + } + ret = regulator_bulk_get(dev, ARRAY_SIZE(supply), res->regul_bulk); + if (ret) { + DRM_ERROR("failed to get regulators\n"); + goto fail; + } + res->regul_count = ARRAY_SIZE(supply); + + return 0; +fail: + DRM_ERROR("HDMI resource init - failed\n"); + return -ENODEV; +} + +static int hdmi_resources_cleanup(struct hdmi_context *hdata) +{ + struct hdmi_resources *res = &hdata->res; + + regulator_bulk_free(res->regul_count, res->regul_bulk); + /* kfree is NULL-safe */ + kfree(res->regul_bulk); + if (!IS_ERR_OR_NULL(res->hdmiphy)) + clk_put(res->hdmiphy); + if (!IS_ERR_OR_NULL(res->sclk_hdmiphy)) + clk_put(res->sclk_hdmiphy); + if (!IS_ERR_OR_NULL(res->sclk_pixel)) + clk_put(res->sclk_pixel); + if (!IS_ERR_OR_NULL(res->sclk_hdmi)) + clk_put(res->sclk_hdmi); + if (!IS_ERR_OR_NULL(res->hdmi)) + clk_put(res->hdmi); + memset(res, 0, sizeof *res); + + return 0; +} + +static void hdmi_resource_poweron(struct hdmi_context *hdata) +{ + struct hdmi_resources *res = &hdata->res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + /* turn HDMI power on */ + regulator_bulk_enable(res->regul_count, res->regul_bulk); + /* power-on hdmi physical interface */ + clk_enable(res->hdmiphy); + /* turn clocks on */ + clk_enable(res->hdmi); + clk_enable(res->sclk_hdmi); + + hdmiphy_conf_reset(hdata); + hdmi_conf_reset(hdata); + hdmi_conf_init(hdata); + +} + +static void hdmi_resource_poweroff(struct hdmi_context *hdata) +{ + struct hdmi_resources *res = &hdata->res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + /* turn clocks off */ + clk_disable(res->sclk_hdmi); + clk_disable(res->hdmi); + /* power-off hdmiphy */ + clk_disable(res->hdmiphy); + /* turn HDMI power off */ + regulator_bulk_disable(res->regul_count, res->regul_bulk); +} + +static int hdmi_runtime_suspend(struct device *dev) +{ + struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); + + DRM_DEBUG_KMS("%s\n", __func__); + + hdmi_resource_poweroff((struct hdmi_context *)ctx->ctx); + + return 0; +} + +static int hdmi_runtime_resume(struct device *dev) +{ + struct exynos_drm_hdmi_context *ctx = get_hdmi_context(dev); + + DRM_DEBUG_KMS("%s\n", __func__); + + hdmi_resource_poweron((struct hdmi_context *)ctx->ctx); + + return 0; +} + +static const struct dev_pm_ops hdmi_pm_ops = { + .runtime_suspend = hdmi_runtime_suspend, + .runtime_resume = hdmi_runtime_resume, +}; + +static struct i2c_client *hdmi_ddc, *hdmi_hdmiphy; + +void hdmi_attach_ddc_client(struct i2c_client *ddc) +{ + if (ddc) + hdmi_ddc = ddc; +} +EXPORT_SYMBOL(hdmi_attach_ddc_client); + +void hdmi_attach_hdmiphy_client(struct i2c_client *hdmiphy) +{ + if (hdmiphy) + hdmi_hdmiphy = hdmiphy; +} +EXPORT_SYMBOL(hdmi_attach_hdmiphy_client); + +static int __devinit hdmi_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct exynos_drm_hdmi_context *drm_hdmi_ctx; + struct hdmi_context *hdata; + struct exynos_drm_hdmi_pdata *pdata; + struct resource *res; + int ret; + + DRM_DEBUG_KMS("[%d]\n", __LINE__); + + pdata = pdev->dev.platform_data; + if (!pdata) { + DRM_ERROR("no platform data specified\n"); + return -EINVAL; + } + + drm_hdmi_ctx = kzalloc(sizeof(*drm_hdmi_ctx), GFP_KERNEL); + if (!drm_hdmi_ctx) { + DRM_ERROR("failed to allocate common hdmi context.\n"); + return -ENOMEM; + } + + hdata = kzalloc(sizeof(struct hdmi_context), GFP_KERNEL); + if (!hdata) { + DRM_ERROR("out of memory\n"); + kfree(drm_hdmi_ctx); + return -ENOMEM; + } + + drm_hdmi_ctx->ctx = (void *)hdata; + hdata->parent_ctx = (void *)drm_hdmi_ctx; + + platform_set_drvdata(pdev, drm_hdmi_ctx); + + hdata->default_win = pdata->default_win; + hdata->default_timing = &pdata->timing; + hdata->default_bpp = pdata->bpp; + hdata->dev = dev; + + ret = hdmi_resources_init(hdata); + if (ret) { + ret = -EINVAL; + goto err_data; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DRM_ERROR("failed to find registers\n"); + ret = -ENOENT; + goto err_resource; + } + + hdata->regs_res = request_mem_region(res->start, resource_size(res), + dev_name(dev)); + if (!hdata->regs_res) { + DRM_ERROR("failed to claim register region\n"); + ret = -ENOENT; + goto err_resource; + } + + hdata->regs = ioremap(res->start, resource_size(res)); + if (!hdata->regs) { + DRM_ERROR("failed to map registers\n"); + ret = -ENXIO; + goto err_req_region; + } + + /* DDC i2c driver */ + if (i2c_add_driver(&ddc_driver)) { + DRM_ERROR("failed to register ddc i2c driver\n"); + ret = -ENOENT; + goto err_iomap; + } + + hdata->ddc_port = hdmi_ddc; + + /* hdmiphy i2c driver */ + if (i2c_add_driver(&hdmiphy_driver)) { + DRM_ERROR("failed to register hdmiphy i2c driver\n"); + ret = -ENOENT; + goto err_ddc; + } + + hdata->hdmiphy_port = hdmi_hdmiphy; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + DRM_ERROR("get interrupt resource failed.\n"); + ret = -ENXIO; + goto err_hdmiphy; + } + + /* create workqueue and hotplug work */ + hdata->wq = alloc_workqueue("exynos-drm-hdmi", + WQ_UNBOUND | WQ_NON_REENTRANT, 1); + if (hdata->wq == NULL) { + DRM_ERROR("Failed to create workqueue.\n"); + ret = -ENOMEM; + goto err_hdmiphy; + } + INIT_WORK(&hdata->hotplug_work, hdmi_hotplug_func); + + /* register hpd interrupt */ + ret = request_irq(res->start, hdmi_irq_handler, 0, "drm_hdmi", + drm_hdmi_ctx); + if (ret) { + DRM_ERROR("request interrupt failed.\n"); + goto err_workqueue; + } + hdata->irq = res->start; + + /* register specific callbacks to common hdmi. */ + exynos_drm_display_ops_register(&display_ops); + exynos_drm_manager_ops_register(&manager_ops); + + hdmi_resource_poweron(hdata); + + return 0; + +err_workqueue: + destroy_workqueue(hdata->wq); +err_hdmiphy: + i2c_del_driver(&hdmiphy_driver); +err_ddc: + i2c_del_driver(&ddc_driver); +err_iomap: + iounmap(hdata->regs); +err_req_region: + release_resource(hdata->regs_res); + kfree(hdata->regs_res); +err_resource: + hdmi_resources_cleanup(hdata); +err_data: + kfree(hdata); + kfree(drm_hdmi_ctx); + return ret; +} + +static int __devexit hdmi_remove(struct platform_device *pdev) +{ + struct exynos_drm_hdmi_context *ctx = platform_get_drvdata(pdev); + struct hdmi_context *hdata = (struct hdmi_context *)ctx->ctx; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + hdmi_resource_poweroff(hdata); + + disable_irq(hdata->irq); + free_irq(hdata->irq, hdata); + + cancel_work_sync(&hdata->hotplug_work); + destroy_workqueue(hdata->wq); + + hdmi_resources_cleanup(hdata); + + iounmap(hdata->regs); + + release_resource(hdata->regs_res); + kfree(hdata->regs_res); + + /* hdmiphy i2c driver */ + i2c_del_driver(&hdmiphy_driver); + /* DDC i2c driver */ + i2c_del_driver(&ddc_driver); + + kfree(hdata); + + return 0; +} + +struct platform_driver hdmi_driver = { + .probe = hdmi_probe, + .remove = __devexit_p(hdmi_remove), + .driver = { + .name = "exynos4-hdmi", + .owner = THIS_MODULE, + .pm = &hdmi_pm_ops, + }, +}; +EXPORT_SYMBOL(hdmi_driver); + +MODULE_AUTHOR("Seung-Woo Kim, <sw0312.kim@samsung.com>"); +MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); +MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); +MODULE_DESCRIPTION("Samsung DRM HDMI core Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/exynos/exynos_hdmi.h b/drivers/gpu/drm/exynos/exynos_hdmi.h new file mode 100644 index 000000000000..31d6cf84c1aa --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_hdmi.h @@ -0,0 +1,87 @@ +/* + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Authors: + * Inki Dae <inki.dae@samsung.com> + * Seung-Woo Kim <sw0312.kim@samsung.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _EXYNOS_HDMI_H_ +#define _EXYNOS_HDMI_H_ + +struct hdmi_conf { + int width; + int height; + int vrefresh; + bool interlace; + const u8 *hdmiphy_data; + const struct hdmi_preset_conf *conf; +}; + +struct hdmi_resources { + struct clk *hdmi; + struct clk *sclk_hdmi; + struct clk *sclk_pixel; + struct clk *sclk_hdmiphy; + struct clk *hdmiphy; + struct regulator_bulk_data *regul_bulk; + int regul_count; +}; + +struct hdmi_context { + struct device *dev; + struct drm_device *drm_dev; + struct fb_videomode *default_timing; + unsigned int default_win; + unsigned int default_bpp; + bool hpd_handle; + bool enabled; + + struct resource *regs_res; + /** base address of HDMI registers */ + void __iomem *regs; + /** HDMI hotplug interrupt */ + unsigned int irq; + /** workqueue for delayed work */ + struct workqueue_struct *wq; + /** hotplug handling work */ + struct work_struct hotplug_work; + + struct i2c_client *ddc_port; + struct i2c_client *hdmiphy_port; + + /** current hdmiphy conf index */ + int cur_conf; + /** other resources */ + struct hdmi_resources res; + + void *parent_ctx; +}; + + +void hdmi_attach_ddc_client(struct i2c_client *ddc); +void hdmi_attach_hdmiphy_client(struct i2c_client *hdmiphy); + +extern struct i2c_driver hdmiphy_driver; +extern struct i2c_driver ddc_driver; + +#endif diff --git a/drivers/gpu/drm/exynos/exynos_hdmiphy.c b/drivers/gpu/drm/exynos/exynos_hdmiphy.c new file mode 100644 index 000000000000..9fe2995ab9f9 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_hdmiphy.c @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Authors: + * Seung-Woo Kim <sw0312.kim@samsung.com> + * Inki Dae <inki.dae@samsung.com> + * + * 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. + * + */ + +#include "drmP.h" + +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/module.h> + +#include "exynos_drm_drv.h" +#include "exynos_hdmi.h" + + +static int hdmiphy_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + hdmi_attach_hdmiphy_client(client); + + dev_info(&client->adapter->dev, "attached s5p_hdmiphy " + "into i2c adapter successfully\n"); + + return 0; +} + +static int hdmiphy_remove(struct i2c_client *client) +{ + dev_info(&client->adapter->dev, "detached s5p_hdmiphy " + "from i2c adapter successfully\n"); + + return 0; +} + +static const struct i2c_device_id hdmiphy_id[] = { + { "s5p_hdmiphy", 0 }, + { }, +}; + +struct i2c_driver hdmiphy_driver = { + .driver = { + .name = "s5p-hdmiphy", + .owner = THIS_MODULE, + }, + .id_table = hdmiphy_id, + .probe = hdmiphy_probe, + .remove = __devexit_p(hdmiphy_remove), + .command = NULL, +}; +EXPORT_SYMBOL(hdmiphy_driver); diff --git a/drivers/gpu/drm/exynos/exynos_mixer.c b/drivers/gpu/drm/exynos/exynos_mixer.c new file mode 100644 index 000000000000..ac24cff39775 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_mixer.c @@ -0,0 +1,1070 @@ +/* + * Copyright (C) 2011 Samsung Electronics Co.Ltd + * Authors: + * Seung-Woo Kim <sw0312.kim@samsung.com> + * Inki Dae <inki.dae@samsung.com> + * Joonyoung Shim <jy0922.shim@samsung.com> + * + * Based on drivers/media/video/s5p-tv/mixer_reg.c + * + * 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. + * + */ + +#include "drmP.h" + +#include "regs-mixer.h" +#include "regs-vp.h" + +#include <linux/kernel.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> + +#include <drm/exynos_drm.h> + +#include "exynos_drm_drv.h" +#include "exynos_drm_hdmi.h" +#include "exynos_hdmi.h" +#include "exynos_mixer.h" + +#define get_mixer_context(dev) platform_get_drvdata(to_platform_device(dev)) + +static const u8 filter_y_horiz_tap8[] = { + 0, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 0, 0, 0, + 0, 2, 4, 5, 6, 6, 6, 6, + 6, 5, 5, 4, 3, 2, 1, 1, + 0, -6, -12, -16, -18, -20, -21, -20, + -20, -18, -16, -13, -10, -8, -5, -2, + 127, 126, 125, 121, 114, 107, 99, 89, + 79, 68, 57, 46, 35, 25, 16, 8, +}; + +static const u8 filter_y_vert_tap4[] = { + 0, -3, -6, -8, -8, -8, -8, -7, + -6, -5, -4, -3, -2, -1, -1, 0, + 127, 126, 124, 118, 111, 102, 92, 81, + 70, 59, 48, 37, 27, 19, 11, 5, + 0, 5, 11, 19, 27, 37, 48, 59, + 70, 81, 92, 102, 111, 118, 124, 126, + 0, 0, -1, -1, -2, -3, -4, -5, + -6, -7, -8, -8, -8, -8, -6, -3, +}; + +static const u8 filter_cr_horiz_tap4[] = { + 0, -3, -6, -8, -8, -8, -8, -7, + -6, -5, -4, -3, -2, -1, -1, 0, + 127, 126, 124, 118, 111, 102, 92, 81, + 70, 59, 48, 37, 27, 19, 11, 5, +}; + +static inline u32 vp_reg_read(struct mixer_resources *res, u32 reg_id) +{ + return readl(res->vp_regs + reg_id); +} + +static inline void vp_reg_write(struct mixer_resources *res, u32 reg_id, + u32 val) +{ + writel(val, res->vp_regs + reg_id); +} + +static inline void vp_reg_writemask(struct mixer_resources *res, u32 reg_id, + u32 val, u32 mask) +{ + u32 old = vp_reg_read(res, reg_id); + + val = (val & mask) | (old & ~mask); + writel(val, res->vp_regs + reg_id); +} + +static inline u32 mixer_reg_read(struct mixer_resources *res, u32 reg_id) +{ + return readl(res->mixer_regs + reg_id); +} + +static inline void mixer_reg_write(struct mixer_resources *res, u32 reg_id, + u32 val) +{ + writel(val, res->mixer_regs + reg_id); +} + +static inline void mixer_reg_writemask(struct mixer_resources *res, + u32 reg_id, u32 val, u32 mask) +{ + u32 old = mixer_reg_read(res, reg_id); + + val = (val & mask) | (old & ~mask); + writel(val, res->mixer_regs + reg_id); +} + +static void mixer_regs_dump(struct mixer_context *ctx) +{ +#define DUMPREG(reg_id) \ +do { \ + DRM_DEBUG_KMS(#reg_id " = %08x\n", \ + (u32)readl(ctx->mixer_res.mixer_regs + reg_id)); \ +} while (0) + + DUMPREG(MXR_STATUS); + DUMPREG(MXR_CFG); + DUMPREG(MXR_INT_EN); + DUMPREG(MXR_INT_STATUS); + + DUMPREG(MXR_LAYER_CFG); + DUMPREG(MXR_VIDEO_CFG); + + DUMPREG(MXR_GRAPHIC0_CFG); + DUMPREG(MXR_GRAPHIC0_BASE); + DUMPREG(MXR_GRAPHIC0_SPAN); + DUMPREG(MXR_GRAPHIC0_WH); + DUMPREG(MXR_GRAPHIC0_SXY); + DUMPREG(MXR_GRAPHIC0_DXY); + + DUMPREG(MXR_GRAPHIC1_CFG); + DUMPREG(MXR_GRAPHIC1_BASE); + DUMPREG(MXR_GRAPHIC1_SPAN); + DUMPREG(MXR_GRAPHIC1_WH); + DUMPREG(MXR_GRAPHIC1_SXY); + DUMPREG(MXR_GRAPHIC1_DXY); +#undef DUMPREG +} + +static void vp_regs_dump(struct mixer_context *ctx) +{ +#define DUMPREG(reg_id) \ +do { \ + DRM_DEBUG_KMS(#reg_id " = %08x\n", \ + (u32) readl(ctx->mixer_res.vp_regs + reg_id)); \ +} while (0) + + DUMPREG(VP_ENABLE); + DUMPREG(VP_SRESET); + DUMPREG(VP_SHADOW_UPDATE); + DUMPREG(VP_FIELD_ID); + DUMPREG(VP_MODE); + DUMPREG(VP_IMG_SIZE_Y); + DUMPREG(VP_IMG_SIZE_C); + DUMPREG(VP_PER_RATE_CTRL); + DUMPREG(VP_TOP_Y_PTR); + DUMPREG(VP_BOT_Y_PTR); + DUMPREG(VP_TOP_C_PTR); + DUMPREG(VP_BOT_C_PTR); + DUMPREG(VP_ENDIAN_MODE); + DUMPREG(VP_SRC_H_POSITION); + DUMPREG(VP_SRC_V_POSITION); + DUMPREG(VP_SRC_WIDTH); + DUMPREG(VP_SRC_HEIGHT); + DUMPREG(VP_DST_H_POSITION); + DUMPREG(VP_DST_V_POSITION); + DUMPREG(VP_DST_WIDTH); + DUMPREG(VP_DST_HEIGHT); + DUMPREG(VP_H_RATIO); + DUMPREG(VP_V_RATIO); + +#undef DUMPREG +} + +static inline void vp_filter_set(struct mixer_resources *res, + int reg_id, const u8 *data, unsigned int size) +{ + /* assure 4-byte align */ + BUG_ON(size & 3); + for (; size; size -= 4, reg_id += 4, data += 4) { + u32 val = (data[0] << 24) | (data[1] << 16) | + (data[2] << 8) | data[3]; + vp_reg_write(res, reg_id, val); + } +} + +static void vp_default_filter(struct mixer_resources *res) +{ + vp_filter_set(res, VP_POLY8_Y0_LL, + filter_y_horiz_tap8, sizeof filter_y_horiz_tap8); + vp_filter_set(res, VP_POLY4_Y0_LL, + filter_y_vert_tap4, sizeof filter_y_vert_tap4); + vp_filter_set(res, VP_POLY4_C0_LL, + filter_cr_horiz_tap4, sizeof filter_cr_horiz_tap4); +} + +static void mixer_vsync_set_update(struct mixer_context *ctx, bool enable) +{ + struct mixer_resources *res = &ctx->mixer_res; + + /* block update on vsync */ + mixer_reg_writemask(res, MXR_STATUS, enable ? + MXR_STATUS_SYNC_ENABLE : 0, MXR_STATUS_SYNC_ENABLE); + + vp_reg_write(res, VP_SHADOW_UPDATE, enable ? + VP_SHADOW_UPDATE_ENABLE : 0); +} + +static void mixer_cfg_scan(struct mixer_context *ctx, unsigned int height) +{ + struct mixer_resources *res = &ctx->mixer_res; + u32 val; + + /* choosing between interlace and progressive mode */ + val = (ctx->interlace ? MXR_CFG_SCAN_INTERLACE : + MXR_CFG_SCAN_PROGRASSIVE); + + /* choosing between porper HD and SD mode */ + if (height == 480) + val |= MXR_CFG_SCAN_NTSC | MXR_CFG_SCAN_SD; + else if (height == 576) + val |= MXR_CFG_SCAN_PAL | MXR_CFG_SCAN_SD; + else if (height == 720) + val |= MXR_CFG_SCAN_HD_720 | MXR_CFG_SCAN_HD; + else if (height == 1080) + val |= MXR_CFG_SCAN_HD_1080 | MXR_CFG_SCAN_HD; + else + val |= MXR_CFG_SCAN_HD_720 | MXR_CFG_SCAN_HD; + + mixer_reg_writemask(res, MXR_CFG, val, MXR_CFG_SCAN_MASK); +} + +static void mixer_cfg_rgb_fmt(struct mixer_context *ctx, unsigned int height) +{ + struct mixer_resources *res = &ctx->mixer_res; + u32 val; + + if (height == 480) { + val = MXR_CFG_RGB601_0_255; + } else if (height == 576) { + val = MXR_CFG_RGB601_0_255; + } else if (height == 720) { + val = MXR_CFG_RGB709_16_235; + mixer_reg_write(res, MXR_CM_COEFF_Y, + (1 << 30) | (94 << 20) | (314 << 10) | + (32 << 0)); + mixer_reg_write(res, MXR_CM_COEFF_CB, + (972 << 20) | (851 << 10) | (225 << 0)); + mixer_reg_write(res, MXR_CM_COEFF_CR, + (225 << 20) | (820 << 10) | (1004 << 0)); + } else if (height == 1080) { + val = MXR_CFG_RGB709_16_235; + mixer_reg_write(res, MXR_CM_COEFF_Y, + (1 << 30) | (94 << 20) | (314 << 10) | + (32 << 0)); + mixer_reg_write(res, MXR_CM_COEFF_CB, + (972 << 20) | (851 << 10) | (225 << 0)); + mixer_reg_write(res, MXR_CM_COEFF_CR, + (225 << 20) | (820 << 10) | (1004 << 0)); + } else { + val = MXR_CFG_RGB709_16_235; + mixer_reg_write(res, MXR_CM_COEFF_Y, + (1 << 30) | (94 << 20) | (314 << 10) | + (32 << 0)); + mixer_reg_write(res, MXR_CM_COEFF_CB, + (972 << 20) | (851 << 10) | (225 << 0)); + mixer_reg_write(res, MXR_CM_COEFF_CR, + (225 << 20) | (820 << 10) | (1004 << 0)); + } + + mixer_reg_writemask(res, MXR_CFG, val, MXR_CFG_RGB_FMT_MASK); +} + +static void mixer_cfg_layer(struct mixer_context *ctx, int win, bool enable) +{ + struct mixer_resources *res = &ctx->mixer_res; + u32 val = enable ? ~0 : 0; + + switch (win) { + case 0: + mixer_reg_writemask(res, MXR_CFG, val, MXR_CFG_GRP0_ENABLE); + break; + case 1: + mixer_reg_writemask(res, MXR_CFG, val, MXR_CFG_GRP1_ENABLE); + break; + case 2: + vp_reg_writemask(res, VP_ENABLE, val, VP_ENABLE_ON); + mixer_reg_writemask(res, MXR_CFG, val, MXR_CFG_VP_ENABLE); + break; + } +} + +static void mixer_run(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + + mixer_reg_writemask(res, MXR_STATUS, ~0, MXR_STATUS_REG_RUN); + + mixer_regs_dump(ctx); +} + +static void vp_video_buffer(struct mixer_context *ctx, int win) +{ + struct mixer_resources *res = &ctx->mixer_res; + unsigned long flags; + struct hdmi_win_data *win_data; + unsigned int full_width, full_height, width, height; + unsigned int x_ratio, y_ratio; + unsigned int src_x_offset, src_y_offset, dst_x_offset, dst_y_offset; + unsigned int mode_width, mode_height; + unsigned int buf_num; + dma_addr_t luma_addr[2], chroma_addr[2]; + bool tiled_mode = false; + bool crcb_mode = false; + u32 val; + + win_data = &ctx->win_data[win]; + + switch (win_data->pixel_format) { + case DRM_FORMAT_NV12MT: + tiled_mode = true; + case DRM_FORMAT_NV12M: + crcb_mode = false; + buf_num = 2; + break; + /* TODO: single buffer format NV12, NV21 */ + default: + /* ignore pixel format at disable time */ + if (!win_data->dma_addr) + break; + + DRM_ERROR("pixel format for vp is wrong [%d].\n", + win_data->pixel_format); + return; + } + + full_width = win_data->fb_width; + full_height = win_data->fb_height; + width = win_data->crtc_width; + height = win_data->crtc_height; + mode_width = win_data->mode_width; + mode_height = win_data->mode_height; + + /* scaling feature: (src << 16) / dst */ + x_ratio = (width << 16) / width; + y_ratio = (height << 16) / height; + + src_x_offset = win_data->fb_x; + src_y_offset = win_data->fb_y; + dst_x_offset = win_data->crtc_x; + dst_y_offset = win_data->crtc_y; + + if (buf_num == 2) { + luma_addr[0] = win_data->dma_addr; + chroma_addr[0] = win_data->chroma_dma_addr; + } else { + luma_addr[0] = win_data->dma_addr; + chroma_addr[0] = win_data->dma_addr + + (full_width * full_height); + } + + if (win_data->scan_flags & DRM_MODE_FLAG_INTERLACE) { + ctx->interlace = true; + if (tiled_mode) { + luma_addr[1] = luma_addr[0] + 0x40; + chroma_addr[1] = chroma_addr[0] + 0x40; + } else { + luma_addr[1] = luma_addr[0] + full_width; + chroma_addr[1] = chroma_addr[0] + full_width; + } + } else { + ctx->interlace = false; + luma_addr[1] = 0; + chroma_addr[1] = 0; + } + + spin_lock_irqsave(&res->reg_slock, flags); + mixer_vsync_set_update(ctx, false); + + /* interlace or progressive scan mode */ + val = (ctx->interlace ? ~0 : 0); + vp_reg_writemask(res, VP_MODE, val, VP_MODE_LINE_SKIP); + + /* setup format */ + val = (crcb_mode ? VP_MODE_NV21 : VP_MODE_NV12); + val |= (tiled_mode ? VP_MODE_MEM_TILED : VP_MODE_MEM_LINEAR); + vp_reg_writemask(res, VP_MODE, val, VP_MODE_FMT_MASK); + + /* setting size of input image */ + vp_reg_write(res, VP_IMG_SIZE_Y, VP_IMG_HSIZE(full_width) | + VP_IMG_VSIZE(full_height)); + /* chroma height has to reduced by 2 to avoid chroma distorions */ + vp_reg_write(res, VP_IMG_SIZE_C, VP_IMG_HSIZE(full_width) | + VP_IMG_VSIZE(full_height / 2)); + + vp_reg_write(res, VP_SRC_WIDTH, width); + vp_reg_write(res, VP_SRC_HEIGHT, height); + vp_reg_write(res, VP_SRC_H_POSITION, + VP_SRC_H_POSITION_VAL(src_x_offset)); + vp_reg_write(res, VP_SRC_V_POSITION, src_y_offset); + + vp_reg_write(res, VP_DST_WIDTH, width); + vp_reg_write(res, VP_DST_H_POSITION, dst_x_offset); + if (ctx->interlace) { + vp_reg_write(res, VP_DST_HEIGHT, height / 2); + vp_reg_write(res, VP_DST_V_POSITION, dst_y_offset / 2); + } else { + vp_reg_write(res, VP_DST_HEIGHT, height); + vp_reg_write(res, VP_DST_V_POSITION, dst_y_offset); + } + + vp_reg_write(res, VP_H_RATIO, x_ratio); + vp_reg_write(res, VP_V_RATIO, y_ratio); + + vp_reg_write(res, VP_ENDIAN_MODE, VP_ENDIAN_MODE_LITTLE); + + /* set buffer address to vp */ + vp_reg_write(res, VP_TOP_Y_PTR, luma_addr[0]); + vp_reg_write(res, VP_BOT_Y_PTR, luma_addr[1]); + vp_reg_write(res, VP_TOP_C_PTR, chroma_addr[0]); + vp_reg_write(res, VP_BOT_C_PTR, chroma_addr[1]); + + mixer_cfg_scan(ctx, mode_height); + mixer_cfg_rgb_fmt(ctx, mode_height); + mixer_cfg_layer(ctx, win, true); + mixer_run(ctx); + + mixer_vsync_set_update(ctx, true); + spin_unlock_irqrestore(&res->reg_slock, flags); + + vp_regs_dump(ctx); +} + +static void mixer_graph_buffer(struct mixer_context *ctx, int win) +{ + struct mixer_resources *res = &ctx->mixer_res; + unsigned long flags; + struct hdmi_win_data *win_data; + unsigned int full_width, width, height; + unsigned int x_ratio, y_ratio; + unsigned int src_x_offset, src_y_offset, dst_x_offset, dst_y_offset; + unsigned int mode_width, mode_height; + dma_addr_t dma_addr; + unsigned int fmt; + u32 val; + + win_data = &ctx->win_data[win]; + + #define RGB565 4 + #define ARGB1555 5 + #define ARGB4444 6 + #define ARGB8888 7 + + switch (win_data->bpp) { + case 16: + fmt = ARGB4444; + break; + case 32: + fmt = ARGB8888; + break; + default: + fmt = ARGB8888; + } + + dma_addr = win_data->dma_addr; + full_width = win_data->fb_width; + width = win_data->crtc_width; + height = win_data->crtc_height; + mode_width = win_data->mode_width; + mode_height = win_data->mode_height; + + /* 2x scaling feature */ + x_ratio = 0; + y_ratio = 0; + + src_x_offset = win_data->fb_x; + src_y_offset = win_data->fb_y; + dst_x_offset = win_data->crtc_x; + dst_y_offset = win_data->crtc_y; + + /* converting dma address base and source offset */ + dma_addr = dma_addr + + (src_x_offset * win_data->bpp >> 3) + + (src_y_offset * full_width * win_data->bpp >> 3); + src_x_offset = 0; + src_y_offset = 0; + + if (win_data->scan_flags & DRM_MODE_FLAG_INTERLACE) + ctx->interlace = true; + else + ctx->interlace = false; + + spin_lock_irqsave(&res->reg_slock, flags); + mixer_vsync_set_update(ctx, false); + + /* setup format */ + mixer_reg_writemask(res, MXR_GRAPHIC_CFG(win), + MXR_GRP_CFG_FORMAT_VAL(fmt), MXR_GRP_CFG_FORMAT_MASK); + + /* setup geometry */ + mixer_reg_write(res, MXR_GRAPHIC_SPAN(win), full_width); + + val = MXR_GRP_WH_WIDTH(width); + val |= MXR_GRP_WH_HEIGHT(height); + val |= MXR_GRP_WH_H_SCALE(x_ratio); + val |= MXR_GRP_WH_V_SCALE(y_ratio); + mixer_reg_write(res, MXR_GRAPHIC_WH(win), val); + + /* setup offsets in source image */ + val = MXR_GRP_SXY_SX(src_x_offset); + val |= MXR_GRP_SXY_SY(src_y_offset); + mixer_reg_write(res, MXR_GRAPHIC_SXY(win), val); + + /* setup offsets in display image */ + val = MXR_GRP_DXY_DX(dst_x_offset); + val |= MXR_GRP_DXY_DY(dst_y_offset); + mixer_reg_write(res, MXR_GRAPHIC_DXY(win), val); + + /* set buffer address to mixer */ + mixer_reg_write(res, MXR_GRAPHIC_BASE(win), dma_addr); + + mixer_cfg_scan(ctx, mode_height); + mixer_cfg_rgb_fmt(ctx, mode_height); + mixer_cfg_layer(ctx, win, true); + mixer_run(ctx); + + mixer_vsync_set_update(ctx, true); + spin_unlock_irqrestore(&res->reg_slock, flags); +} + +static void vp_win_reset(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + int tries = 100; + + vp_reg_write(res, VP_SRESET, VP_SRESET_PROCESSING); + for (tries = 100; tries; --tries) { + /* waiting until VP_SRESET_PROCESSING is 0 */ + if (~vp_reg_read(res, VP_SRESET) & VP_SRESET_PROCESSING) + break; + mdelay(10); + } + WARN(tries == 0, "failed to reset Video Processor\n"); +} + +static int mixer_enable_vblank(void *ctx, int pipe) +{ + struct mixer_context *mixer_ctx = ctx; + struct mixer_resources *res = &mixer_ctx->mixer_res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + mixer_ctx->pipe = pipe; + + /* enable vsync interrupt */ + mixer_reg_writemask(res, MXR_INT_EN, MXR_INT_EN_VSYNC, + MXR_INT_EN_VSYNC); + + return 0; +} + +static void mixer_disable_vblank(void *ctx) +{ + struct mixer_context *mixer_ctx = ctx; + struct mixer_resources *res = &mixer_ctx->mixer_res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + /* disable vsync interrupt */ + mixer_reg_writemask(res, MXR_INT_EN, 0, MXR_INT_EN_VSYNC); +} + +static void mixer_win_mode_set(void *ctx, + struct exynos_drm_overlay *overlay) +{ + struct mixer_context *mixer_ctx = ctx; + struct hdmi_win_data *win_data; + int win; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + if (!overlay) { + DRM_ERROR("overlay is NULL\n"); + return; + } + + DRM_DEBUG_KMS("set [%d]x[%d] at (%d,%d) to [%d]x[%d] at (%d,%d)\n", + overlay->fb_width, overlay->fb_height, + overlay->fb_x, overlay->fb_y, + overlay->crtc_width, overlay->crtc_height, + overlay->crtc_x, overlay->crtc_y); + + win = overlay->zpos; + if (win == DEFAULT_ZPOS) + win = mixer_ctx->default_win; + + if (win < 0 || win > HDMI_OVERLAY_NUMBER) { + DRM_ERROR("overlay plane[%d] is wrong\n", win); + return; + } + + win_data = &mixer_ctx->win_data[win]; + + win_data->dma_addr = overlay->dma_addr[0]; + win_data->vaddr = overlay->vaddr[0]; + win_data->chroma_dma_addr = overlay->dma_addr[1]; + win_data->chroma_vaddr = overlay->vaddr[1]; + win_data->pixel_format = overlay->pixel_format; + win_data->bpp = overlay->bpp; + + win_data->crtc_x = overlay->crtc_x; + win_data->crtc_y = overlay->crtc_y; + win_data->crtc_width = overlay->crtc_width; + win_data->crtc_height = overlay->crtc_height; + + win_data->fb_x = overlay->fb_x; + win_data->fb_y = overlay->fb_y; + win_data->fb_width = overlay->fb_width; + win_data->fb_height = overlay->fb_height; + + win_data->mode_width = overlay->mode_width; + win_data->mode_height = overlay->mode_height; + + win_data->scan_flags = overlay->scan_flag; +} + +static void mixer_win_commit(void *ctx, int zpos) +{ + struct mixer_context *mixer_ctx = ctx; + int win = zpos; + + DRM_DEBUG_KMS("[%d] %s, win: %d\n", __LINE__, __func__, win); + + if (win == DEFAULT_ZPOS) + win = mixer_ctx->default_win; + + if (win < 0 || win > HDMI_OVERLAY_NUMBER) { + DRM_ERROR("overlay plane[%d] is wrong\n", win); + return; + } + + if (win > 1) + vp_video_buffer(mixer_ctx, win); + else + mixer_graph_buffer(mixer_ctx, win); +} + +static void mixer_win_disable(void *ctx, int zpos) +{ + struct mixer_context *mixer_ctx = ctx; + struct mixer_resources *res = &mixer_ctx->mixer_res; + unsigned long flags; + int win = zpos; + + DRM_DEBUG_KMS("[%d] %s, win: %d\n", __LINE__, __func__, win); + + if (win == DEFAULT_ZPOS) + win = mixer_ctx->default_win; + + if (win < 0 || win > HDMI_OVERLAY_NUMBER) { + DRM_ERROR("overlay plane[%d] is wrong\n", win); + return; + } + + spin_lock_irqsave(&res->reg_slock, flags); + mixer_vsync_set_update(mixer_ctx, false); + + mixer_cfg_layer(mixer_ctx, win, false); + + mixer_vsync_set_update(mixer_ctx, true); + spin_unlock_irqrestore(&res->reg_slock, flags); +} + +static struct exynos_hdmi_overlay_ops overlay_ops = { + .enable_vblank = mixer_enable_vblank, + .disable_vblank = mixer_disable_vblank, + .win_mode_set = mixer_win_mode_set, + .win_commit = mixer_win_commit, + .win_disable = mixer_win_disable, +}; + +/* for pageflip event */ +static void mixer_finish_pageflip(struct drm_device *drm_dev, int crtc) +{ + struct exynos_drm_private *dev_priv = drm_dev->dev_private; + struct drm_pending_vblank_event *e, *t; + struct timeval now; + unsigned long flags; + bool is_checked = false; + + spin_lock_irqsave(&drm_dev->event_lock, flags); + + list_for_each_entry_safe(e, t, &dev_priv->pageflip_event_list, + base.link) { + /* if event's pipe isn't same as crtc then ignore it. */ + if (crtc != e->pipe) + continue; + + is_checked = true; + do_gettimeofday(&now); + e->event.sequence = 0; + e->event.tv_sec = now.tv_sec; + e->event.tv_usec = now.tv_usec; + + list_move_tail(&e->base.link, &e->base.file_priv->event_list); + wake_up_interruptible(&e->base.file_priv->event_wait); + } + + if (is_checked) + drm_vblank_put(drm_dev, crtc); + + spin_unlock_irqrestore(&drm_dev->event_lock, flags); +} + +static irqreturn_t mixer_irq_handler(int irq, void *arg) +{ + struct exynos_drm_hdmi_context *drm_hdmi_ctx = arg; + struct mixer_context *ctx = + (struct mixer_context *)drm_hdmi_ctx->ctx; + struct mixer_resources *res = &ctx->mixer_res; + u32 val, val_base; + + spin_lock(&res->reg_slock); + + /* read interrupt status for handling and clearing flags for VSYNC */ + val = mixer_reg_read(res, MXR_INT_STATUS); + + /* handling VSYNC */ + if (val & MXR_INT_STATUS_VSYNC) { + /* interlace scan need to check shadow register */ + if (ctx->interlace) { + val_base = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(0)); + if (ctx->win_data[0].dma_addr != val_base) + goto out; + + val_base = mixer_reg_read(res, MXR_GRAPHIC_BASE_S(1)); + if (ctx->win_data[1].dma_addr != val_base) + goto out; + } + + drm_handle_vblank(drm_hdmi_ctx->drm_dev, ctx->pipe); + mixer_finish_pageflip(drm_hdmi_ctx->drm_dev, ctx->pipe); + } + +out: + /* clear interrupts */ + if (~val & MXR_INT_EN_VSYNC) { + /* vsync interrupt use different bit for read and clear */ + val &= ~MXR_INT_EN_VSYNC; + val |= MXR_INT_CLEAR_VSYNC; + } + mixer_reg_write(res, MXR_INT_STATUS, val); + + spin_unlock(&res->reg_slock); + + return IRQ_HANDLED; +} + +static void mixer_win_reset(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + unsigned long flags; + u32 val; /* value stored to register */ + + spin_lock_irqsave(&res->reg_slock, flags); + mixer_vsync_set_update(ctx, false); + + mixer_reg_writemask(res, MXR_CFG, MXR_CFG_DST_HDMI, MXR_CFG_DST_MASK); + + /* set output in RGB888 mode */ + mixer_reg_writemask(res, MXR_CFG, MXR_CFG_OUT_RGB888, MXR_CFG_OUT_MASK); + + /* 16 beat burst in DMA */ + mixer_reg_writemask(res, MXR_STATUS, MXR_STATUS_16_BURST, + MXR_STATUS_BURST_MASK); + + /* setting default layer priority: layer1 > video > layer0 + * because typical usage scenario would be + * layer0 - framebuffer + * video - video overlay + * layer1 - OSD + */ + val = MXR_LAYER_CFG_GRP0_VAL(1); + val |= MXR_LAYER_CFG_VP_VAL(2); + val |= MXR_LAYER_CFG_GRP1_VAL(3); + mixer_reg_write(res, MXR_LAYER_CFG, val); + + /* setting background color */ + mixer_reg_write(res, MXR_BG_COLOR0, 0x008080); + mixer_reg_write(res, MXR_BG_COLOR1, 0x008080); + mixer_reg_write(res, MXR_BG_COLOR2, 0x008080); + + /* setting graphical layers */ + + val = MXR_GRP_CFG_COLOR_KEY_DISABLE; /* no blank key */ + val |= MXR_GRP_CFG_WIN_BLEND_EN; + val |= MXR_GRP_CFG_ALPHA_VAL(0xff); /* non-transparent alpha */ + + /* the same configuration for both layers */ + mixer_reg_write(res, MXR_GRAPHIC_CFG(0), val); + + val |= MXR_GRP_CFG_BLEND_PRE_MUL; + val |= MXR_GRP_CFG_PIXEL_BLEND_EN; + mixer_reg_write(res, MXR_GRAPHIC_CFG(1), val); + + /* configuration of Video Processor Registers */ + vp_win_reset(ctx); + vp_default_filter(res); + + /* disable all layers */ + mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_GRP0_ENABLE); + mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_GRP1_ENABLE); + mixer_reg_writemask(res, MXR_CFG, 0, MXR_CFG_VP_ENABLE); + + mixer_vsync_set_update(ctx, true); + spin_unlock_irqrestore(&res->reg_slock, flags); +} + +static void mixer_resource_poweron(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + clk_enable(res->mixer); + clk_enable(res->vp); + clk_enable(res->sclk_mixer); + + mixer_win_reset(ctx); +} + +static void mixer_resource_poweroff(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + + DRM_DEBUG_KMS("[%d] %s\n", __LINE__, __func__); + + clk_disable(res->mixer); + clk_disable(res->vp); + clk_disable(res->sclk_mixer); +} + +static int mixer_runtime_resume(struct device *dev) +{ + struct exynos_drm_hdmi_context *ctx = get_mixer_context(dev); + + DRM_DEBUG_KMS("resume - start\n"); + + mixer_resource_poweron((struct mixer_context *)ctx->ctx); + + return 0; +} + +static int mixer_runtime_suspend(struct device *dev) +{ + struct exynos_drm_hdmi_context *ctx = get_mixer_context(dev); + + DRM_DEBUG_KMS("suspend - start\n"); + + mixer_resource_poweroff((struct mixer_context *)ctx->ctx); + + return 0; +} + +static const struct dev_pm_ops mixer_pm_ops = { + .runtime_suspend = mixer_runtime_suspend, + .runtime_resume = mixer_runtime_resume, +}; + +static int __devinit mixer_resources_init(struct exynos_drm_hdmi_context *ctx, + struct platform_device *pdev) +{ + struct mixer_context *mixer_ctx = + (struct mixer_context *)ctx->ctx; + struct device *dev = &pdev->dev; + struct mixer_resources *mixer_res = &mixer_ctx->mixer_res; + struct resource *res; + int ret; + + mixer_res->dev = dev; + spin_lock_init(&mixer_res->reg_slock); + + mixer_res->mixer = clk_get(dev, "mixer"); + if (IS_ERR_OR_NULL(mixer_res->mixer)) { + dev_err(dev, "failed to get clock 'mixer'\n"); + ret = -ENODEV; + goto fail; + } + mixer_res->vp = clk_get(dev, "vp"); + if (IS_ERR_OR_NULL(mixer_res->vp)) { + dev_err(dev, "failed to get clock 'vp'\n"); + ret = -ENODEV; + goto fail; + } + mixer_res->sclk_mixer = clk_get(dev, "sclk_mixer"); + if (IS_ERR_OR_NULL(mixer_res->sclk_mixer)) { + dev_err(dev, "failed to get clock 'sclk_mixer'\n"); + ret = -ENODEV; + goto fail; + } + mixer_res->sclk_hdmi = clk_get(dev, "sclk_hdmi"); + if (IS_ERR_OR_NULL(mixer_res->sclk_hdmi)) { + dev_err(dev, "failed to get clock 'sclk_hdmi'\n"); + ret = -ENODEV; + goto fail; + } + mixer_res->sclk_dac = clk_get(dev, "sclk_dac"); + if (IS_ERR_OR_NULL(mixer_res->sclk_dac)) { + dev_err(dev, "failed to get clock 'sclk_dac'\n"); + ret = -ENODEV; + goto fail; + } + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mxr"); + if (res == NULL) { + dev_err(dev, "get memory resource failed.\n"); + ret = -ENXIO; + goto fail; + } + + clk_set_parent(mixer_res->sclk_mixer, mixer_res->sclk_hdmi); + + mixer_res->mixer_regs = ioremap(res->start, resource_size(res)); + if (mixer_res->mixer_regs == NULL) { + dev_err(dev, "register mapping failed.\n"); + ret = -ENXIO; + goto fail; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vp"); + if (res == NULL) { + dev_err(dev, "get memory resource failed.\n"); + ret = -ENXIO; + goto fail_mixer_regs; + } + + mixer_res->vp_regs = ioremap(res->start, resource_size(res)); + if (mixer_res->vp_regs == NULL) { + dev_err(dev, "register mapping failed.\n"); + ret = -ENXIO; + goto fail_mixer_regs; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "irq"); + if (res == NULL) { + dev_err(dev, "get interrupt resource failed.\n"); + ret = -ENXIO; + goto fail_vp_regs; + } + + ret = request_irq(res->start, mixer_irq_handler, 0, "drm_mixer", ctx); + if (ret) { + dev_err(dev, "request interrupt failed.\n"); + goto fail_vp_regs; + } + mixer_res->irq = res->start; + + return 0; + +fail_vp_regs: + iounmap(mixer_res->vp_regs); + +fail_mixer_regs: + iounmap(mixer_res->mixer_regs); + +fail: + if (!IS_ERR_OR_NULL(mixer_res->sclk_dac)) + clk_put(mixer_res->sclk_dac); + if (!IS_ERR_OR_NULL(mixer_res->sclk_hdmi)) + clk_put(mixer_res->sclk_hdmi); + if (!IS_ERR_OR_NULL(mixer_res->sclk_mixer)) + clk_put(mixer_res->sclk_mixer); + if (!IS_ERR_OR_NULL(mixer_res->vp)) + clk_put(mixer_res->vp); + if (!IS_ERR_OR_NULL(mixer_res->mixer)) + clk_put(mixer_res->mixer); + mixer_res->dev = NULL; + return ret; +} + +static void mixer_resources_cleanup(struct mixer_context *ctx) +{ + struct mixer_resources *res = &ctx->mixer_res; + + disable_irq(res->irq); + free_irq(res->irq, ctx); + + iounmap(res->vp_regs); + iounmap(res->mixer_regs); +} + +static int __devinit mixer_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct exynos_drm_hdmi_context *drm_hdmi_ctx; + struct mixer_context *ctx; + int ret; + + dev_info(dev, "probe start\n"); + + drm_hdmi_ctx = kzalloc(sizeof(*drm_hdmi_ctx), GFP_KERNEL); + if (!drm_hdmi_ctx) { + DRM_ERROR("failed to allocate common hdmi context.\n"); + return -ENOMEM; + } + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + DRM_ERROR("failed to alloc mixer context.\n"); + kfree(drm_hdmi_ctx); + return -ENOMEM; + } + + drm_hdmi_ctx->ctx = (void *)ctx; + + platform_set_drvdata(pdev, drm_hdmi_ctx); + + /* acquire resources: regs, irqs, clocks */ + ret = mixer_resources_init(drm_hdmi_ctx, pdev); + if (ret) + goto fail; + + /* register specific callback point to common hdmi. */ + exynos_drm_overlay_ops_register(&overlay_ops); + + mixer_resource_poweron(ctx); + + return 0; + + +fail: + dev_info(dev, "probe failed\n"); + return ret; +} + +static int mixer_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct exynos_drm_hdmi_context *drm_hdmi_ctx = + platform_get_drvdata(pdev); + struct mixer_context *ctx = (struct mixer_context *)drm_hdmi_ctx->ctx; + + dev_info(dev, "remove sucessful\n"); + + mixer_resource_poweroff(ctx); + mixer_resources_cleanup(ctx); + + return 0; +} + +struct platform_driver mixer_driver = { + .driver = { + .name = "s5p-mixer", + .owner = THIS_MODULE, + .pm = &mixer_pm_ops, + }, + .probe = mixer_probe, + .remove = __devexit_p(mixer_remove), +}; +EXPORT_SYMBOL(mixer_driver); + +MODULE_AUTHOR("Seung-Woo Kim, <sw0312.kim@samsung.com>"); +MODULE_AUTHOR("Inki Dae <inki.dae@samsung.com>"); +MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); +MODULE_DESCRIPTION("Samsung DRM HDMI mixer Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/exynos/exynos_mixer.h b/drivers/gpu/drm/exynos/exynos_mixer.h new file mode 100644 index 000000000000..cebacfefc077 --- /dev/null +++ b/drivers/gpu/drm/exynos/exynos_mixer.h @@ -0,0 +1,92 @@ +/* + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Authors: + * Seung-Woo Kim <sw0312.kim@samsung.com> + * Inki Dae <inki.dae@samsung.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _EXYNOS_MIXER_H_ +#define _EXYNOS_MIXER_H_ + +#define HDMI_OVERLAY_NUMBER 3 + +struct hdmi_win_data { + dma_addr_t dma_addr; + void __iomem *vaddr; + dma_addr_t chroma_dma_addr; + void __iomem *chroma_vaddr; + uint32_t pixel_format; + unsigned int bpp; + unsigned int crtc_x; + unsigned int crtc_y; + unsigned int crtc_width; + unsigned int crtc_height; + unsigned int fb_x; + unsigned int fb_y; + unsigned int fb_width; + unsigned int fb_height; + unsigned int mode_width; + unsigned int mode_height; + unsigned int scan_flags; +}; + +struct mixer_resources { + struct device *dev; + /** interrupt index */ + int irq; + /** pointer to Mixer registers */ + void __iomem *mixer_regs; + /** pointer to Video Processor registers */ + void __iomem *vp_regs; + /** spinlock for protection of registers */ + spinlock_t reg_slock; + /** other resources */ + struct clk *mixer; + struct clk *vp; + struct clk *sclk_mixer; + struct clk *sclk_hdmi; + struct clk *sclk_dac; +}; + +struct mixer_context { + unsigned int default_win; + struct fb_videomode *default_timing; + unsigned int default_bpp; + + /** mixer interrupt */ + unsigned int irq; + /** current crtc pipe for vblank */ + int pipe; + /** interlace scan mode */ + bool interlace; + /** vp enabled status */ + bool vp_enabled; + + /** mixer and vp resources */ + struct mixer_resources mixer_res; + + /** overlay window data */ + struct hdmi_win_data win_data[HDMI_OVERLAY_NUMBER]; +}; + +#endif diff --git a/drivers/gpu/drm/exynos/regs-hdmi.h b/drivers/gpu/drm/exynos/regs-hdmi.h new file mode 100644 index 000000000000..72e6b52be740 --- /dev/null +++ b/drivers/gpu/drm/exynos/regs-hdmi.h @@ -0,0 +1,147 @@ +/* + * + * Cloned from drivers/media/video/s5p-tv/regs-hdmi.h + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * HDMI register header file for Samsung TVOUT driver + * + * 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. +*/ + +#ifndef SAMSUNG_REGS_HDMI_H +#define SAMSUNG_REGS_HDMI_H + +/* + * Register part +*/ + +#define HDMI_CTRL_BASE(x) ((x) + 0x00000000) +#define HDMI_CORE_BASE(x) ((x) + 0x00010000) +#define HDMI_TG_BASE(x) ((x) + 0x00050000) + +/* Control registers */ +#define HDMI_INTC_CON HDMI_CTRL_BASE(0x0000) +#define HDMI_INTC_FLAG HDMI_CTRL_BASE(0x0004) +#define HDMI_HPD_STATUS HDMI_CTRL_BASE(0x000C) +#define HDMI_PHY_RSTOUT HDMI_CTRL_BASE(0x0014) +#define HDMI_PHY_VPLL HDMI_CTRL_BASE(0x0018) +#define HDMI_PHY_CMU HDMI_CTRL_BASE(0x001C) +#define HDMI_CORE_RSTOUT HDMI_CTRL_BASE(0x0020) + +/* Core registers */ +#define HDMI_CON_0 HDMI_CORE_BASE(0x0000) +#define HDMI_CON_1 HDMI_CORE_BASE(0x0004) +#define HDMI_CON_2 HDMI_CORE_BASE(0x0008) +#define HDMI_SYS_STATUS HDMI_CORE_BASE(0x0010) +#define HDMI_PHY_STATUS HDMI_CORE_BASE(0x0014) +#define HDMI_STATUS_EN HDMI_CORE_BASE(0x0020) +#define HDMI_HPD HDMI_CORE_BASE(0x0030) +#define HDMI_MODE_SEL HDMI_CORE_BASE(0x0040) +#define HDMI_BLUE_SCREEN_0 HDMI_CORE_BASE(0x0050) +#define HDMI_BLUE_SCREEN_1 HDMI_CORE_BASE(0x0054) +#define HDMI_BLUE_SCREEN_2 HDMI_CORE_BASE(0x0058) +#define HDMI_H_BLANK_0 HDMI_CORE_BASE(0x00A0) +#define HDMI_H_BLANK_1 HDMI_CORE_BASE(0x00A4) +#define HDMI_V_BLANK_0 HDMI_CORE_BASE(0x00B0) +#define HDMI_V_BLANK_1 HDMI_CORE_BASE(0x00B4) +#define HDMI_V_BLANK_2 HDMI_CORE_BASE(0x00B8) +#define HDMI_H_V_LINE_0 HDMI_CORE_BASE(0x00C0) +#define HDMI_H_V_LINE_1 HDMI_CORE_BASE(0x00C4) +#define HDMI_H_V_LINE_2 HDMI_CORE_BASE(0x00C8) +#define HDMI_VSYNC_POL HDMI_CORE_BASE(0x00E4) +#define HDMI_INT_PRO_MODE HDMI_CORE_BASE(0x00E8) +#define HDMI_V_BLANK_F_0 HDMI_CORE_BASE(0x0110) +#define HDMI_V_BLANK_F_1 HDMI_CORE_BASE(0x0114) +#define HDMI_V_BLANK_F_2 HDMI_CORE_BASE(0x0118) +#define HDMI_H_SYNC_GEN_0 HDMI_CORE_BASE(0x0120) +#define HDMI_H_SYNC_GEN_1 HDMI_CORE_BASE(0x0124) +#define HDMI_H_SYNC_GEN_2 HDMI_CORE_BASE(0x0128) +#define HDMI_V_SYNC_GEN_1_0 HDMI_CORE_BASE(0x0130) +#define HDMI_V_SYNC_GEN_1_1 HDMI_CORE_BASE(0x0134) +#define HDMI_V_SYNC_GEN_1_2 HDMI_CORE_BASE(0x0138) +#define HDMI_V_SYNC_GEN_2_0 HDMI_CORE_BASE(0x0140) +#define HDMI_V_SYNC_GEN_2_1 HDMI_CORE_BASE(0x0144) +#define HDMI_V_SYNC_GEN_2_2 HDMI_CORE_BASE(0x0148) +#define HDMI_V_SYNC_GEN_3_0 HDMI_CORE_BASE(0x0150) +#define HDMI_V_SYNC_GEN_3_1 HDMI_CORE_BASE(0x0154) +#define HDMI_V_SYNC_GEN_3_2 HDMI_CORE_BASE(0x0158) +#define HDMI_ACR_CON HDMI_CORE_BASE(0x0180) +#define HDMI_AVI_CON HDMI_CORE_BASE(0x0300) +#define HDMI_AVI_BYTE(n) HDMI_CORE_BASE(0x0320 + 4 * (n)) +#define HDMI_DC_CONTROL HDMI_CORE_BASE(0x05C0) +#define HDMI_VIDEO_PATTERN_GEN HDMI_CORE_BASE(0x05C4) +#define HDMI_HPD_GEN HDMI_CORE_BASE(0x05C8) +#define HDMI_AUI_CON HDMI_CORE_BASE(0x0360) +#define HDMI_SPD_CON HDMI_CORE_BASE(0x0400) + +/* Timing generator registers */ +#define HDMI_TG_CMD HDMI_TG_BASE(0x0000) +#define HDMI_TG_H_FSZ_L HDMI_TG_BASE(0x0018) +#define HDMI_TG_H_FSZ_H HDMI_TG_BASE(0x001C) +#define HDMI_TG_HACT_ST_L HDMI_TG_BASE(0x0020) +#define HDMI_TG_HACT_ST_H HDMI_TG_BASE(0x0024) +#define HDMI_TG_HACT_SZ_L HDMI_TG_BASE(0x0028) +#define HDMI_TG_HACT_SZ_H HDMI_TG_BASE(0x002C) +#define HDMI_TG_V_FSZ_L HDMI_TG_BASE(0x0030) +#define HDMI_TG_V_FSZ_H HDMI_TG_BASE(0x0034) +#define HDMI_TG_VSYNC_L HDMI_TG_BASE(0x0038) +#define HDMI_TG_VSYNC_H HDMI_TG_BASE(0x003C) +#define HDMI_TG_VSYNC2_L HDMI_TG_BASE(0x0040) +#define HDMI_TG_VSYNC2_H HDMI_TG_BASE(0x0044) +#define HDMI_TG_VACT_ST_L HDMI_TG_BASE(0x0048) +#define HDMI_TG_VACT_ST_H HDMI_TG_BASE(0x004C) +#define HDMI_TG_VACT_SZ_L HDMI_TG_BASE(0x0050) +#define HDMI_TG_VACT_SZ_H HDMI_TG_BASE(0x0054) +#define HDMI_TG_FIELD_CHG_L HDMI_TG_BASE(0x0058) +#define HDMI_TG_FIELD_CHG_H HDMI_TG_BASE(0x005C) +#define HDMI_TG_VACT_ST2_L HDMI_TG_BASE(0x0060) +#define HDMI_TG_VACT_ST2_H HDMI_TG_BASE(0x0064) +#define HDMI_TG_VSYNC_TOP_HDMI_L HDMI_TG_BASE(0x0078) +#define HDMI_TG_VSYNC_TOP_HDMI_H HDMI_TG_BASE(0x007C) +#define HDMI_TG_VSYNC_BOT_HDMI_L HDMI_TG_BASE(0x0080) +#define HDMI_TG_VSYNC_BOT_HDMI_H HDMI_TG_BASE(0x0084) +#define HDMI_TG_FIELD_TOP_HDMI_L HDMI_TG_BASE(0x0088) +#define HDMI_TG_FIELD_TOP_HDMI_H HDMI_TG_BASE(0x008C) +#define HDMI_TG_FIELD_BOT_HDMI_L HDMI_TG_BASE(0x0090) +#define HDMI_TG_FIELD_BOT_HDMI_H HDMI_TG_BASE(0x0094) + +/* + * Bit definition part + */ + +/* HDMI_INTC_CON */ +#define HDMI_INTC_EN_GLOBAL (1 << 6) +#define HDMI_INTC_EN_HPD_PLUG (1 << 3) +#define HDMI_INTC_EN_HPD_UNPLUG (1 << 2) + +/* HDMI_INTC_FLAG */ +#define HDMI_INTC_FLAG_HPD_PLUG (1 << 3) +#define HDMI_INTC_FLAG_HPD_UNPLUG (1 << 2) + +/* HDMI_PHY_RSTOUT */ +#define HDMI_PHY_SW_RSTOUT (1 << 0) + +/* HDMI_CORE_RSTOUT */ +#define HDMI_CORE_SW_RSTOUT (1 << 0) + +/* HDMI_CON_0 */ +#define HDMI_BLUE_SCR_EN (1 << 5) +#define HDMI_EN (1 << 0) + +/* HDMI_PHY_STATUS */ +#define HDMI_PHY_STATUS_READY (1 << 0) + +/* HDMI_MODE_SEL */ +#define HDMI_MODE_HDMI_EN (1 << 1) +#define HDMI_MODE_DVI_EN (1 << 0) +#define HDMI_MODE_MASK (3 << 0) + +/* HDMI_TG_CMD */ +#define HDMI_TG_EN (1 << 0) +#define HDMI_FIELD_EN (1 << 1) + +#endif /* SAMSUNG_REGS_HDMI_H */ diff --git a/drivers/gpu/drm/exynos/regs-mixer.h b/drivers/gpu/drm/exynos/regs-mixer.h new file mode 100644 index 000000000000..fd2f4d14cf6d --- /dev/null +++ b/drivers/gpu/drm/exynos/regs-mixer.h @@ -0,0 +1,141 @@ +/* + * + * Cloned from drivers/media/video/s5p-tv/regs-mixer.h + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Mixer register header file for Samsung Mixer driver + * + * 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. +*/ +#ifndef SAMSUNG_REGS_MIXER_H +#define SAMSUNG_REGS_MIXER_H + +/* + * Register part + */ +#define MXR_STATUS 0x0000 +#define MXR_CFG 0x0004 +#define MXR_INT_EN 0x0008 +#define MXR_INT_STATUS 0x000C +#define MXR_LAYER_CFG 0x0010 +#define MXR_VIDEO_CFG 0x0014 +#define MXR_GRAPHIC0_CFG 0x0020 +#define MXR_GRAPHIC0_BASE 0x0024 +#define MXR_GRAPHIC0_SPAN 0x0028 +#define MXR_GRAPHIC0_SXY 0x002C +#define MXR_GRAPHIC0_WH 0x0030 +#define MXR_GRAPHIC0_DXY 0x0034 +#define MXR_GRAPHIC0_BLANK 0x0038 +#define MXR_GRAPHIC1_CFG 0x0040 +#define MXR_GRAPHIC1_BASE 0x0044 +#define MXR_GRAPHIC1_SPAN 0x0048 +#define MXR_GRAPHIC1_SXY 0x004C +#define MXR_GRAPHIC1_WH 0x0050 +#define MXR_GRAPHIC1_DXY 0x0054 +#define MXR_GRAPHIC1_BLANK 0x0058 +#define MXR_BG_CFG 0x0060 +#define MXR_BG_COLOR0 0x0064 +#define MXR_BG_COLOR1 0x0068 +#define MXR_BG_COLOR2 0x006C +#define MXR_CM_COEFF_Y 0x0080 +#define MXR_CM_COEFF_CB 0x0084 +#define MXR_CM_COEFF_CR 0x0088 +#define MXR_GRAPHIC0_BASE_S 0x2024 +#define MXR_GRAPHIC1_BASE_S 0x2044 + +/* for parametrized access to layer registers */ +#define MXR_GRAPHIC_CFG(i) (0x0020 + (i) * 0x20) +#define MXR_GRAPHIC_BASE(i) (0x0024 + (i) * 0x20) +#define MXR_GRAPHIC_SPAN(i) (0x0028 + (i) * 0x20) +#define MXR_GRAPHIC_SXY(i) (0x002C + (i) * 0x20) +#define MXR_GRAPHIC_WH(i) (0x0030 + (i) * 0x20) +#define MXR_GRAPHIC_DXY(i) (0x0034 + (i) * 0x20) +#define MXR_GRAPHIC_BLANK(i) (0x0038 + (i) * 0x20) +#define MXR_GRAPHIC_BASE_S(i) (0x2024 + (i) * 0x20) + +/* + * Bit definition part + */ + +/* generates mask for range of bits */ +#define MXR_MASK(high_bit, low_bit) \ + (((2 << ((high_bit) - (low_bit))) - 1) << (low_bit)) + +#define MXR_MASK_VAL(val, high_bit, low_bit) \ + (((val) << (low_bit)) & MXR_MASK(high_bit, low_bit)) + +/* bits for MXR_STATUS */ +#define MXR_STATUS_16_BURST (1 << 7) +#define MXR_STATUS_BURST_MASK (1 << 7) +#define MXR_STATUS_BIG_ENDIAN (1 << 3) +#define MXR_STATUS_ENDIAN_MASK (1 << 3) +#define MXR_STATUS_SYNC_ENABLE (1 << 2) +#define MXR_STATUS_REG_RUN (1 << 0) + +/* bits for MXR_CFG */ +#define MXR_CFG_RGB601_0_255 (0 << 9) +#define MXR_CFG_RGB601_16_235 (1 << 9) +#define MXR_CFG_RGB709_0_255 (2 << 9) +#define MXR_CFG_RGB709_16_235 (3 << 9) +#define MXR_CFG_RGB_FMT_MASK 0x600 +#define MXR_CFG_OUT_YUV444 (0 << 8) +#define MXR_CFG_OUT_RGB888 (1 << 8) +#define MXR_CFG_OUT_MASK (1 << 8) +#define MXR_CFG_DST_SDO (0 << 7) +#define MXR_CFG_DST_HDMI (1 << 7) +#define MXR_CFG_DST_MASK (1 << 7) +#define MXR_CFG_SCAN_HD_720 (0 << 6) +#define MXR_CFG_SCAN_HD_1080 (1 << 6) +#define MXR_CFG_GRP1_ENABLE (1 << 5) +#define MXR_CFG_GRP0_ENABLE (1 << 4) +#define MXR_CFG_VP_ENABLE (1 << 3) +#define MXR_CFG_SCAN_INTERLACE (0 << 2) +#define MXR_CFG_SCAN_PROGRASSIVE (1 << 2) +#define MXR_CFG_SCAN_NTSC (0 << 1) +#define MXR_CFG_SCAN_PAL (1 << 1) +#define MXR_CFG_SCAN_SD (0 << 0) +#define MXR_CFG_SCAN_HD (1 << 0) +#define MXR_CFG_SCAN_MASK 0x47 + +/* bits for MXR_GRAPHICn_CFG */ +#define MXR_GRP_CFG_COLOR_KEY_DISABLE (1 << 21) +#define MXR_GRP_CFG_BLEND_PRE_MUL (1 << 20) +#define MXR_GRP_CFG_WIN_BLEND_EN (1 << 17) +#define MXR_GRP_CFG_PIXEL_BLEND_EN (1 << 16) +#define MXR_GRP_CFG_FORMAT_VAL(x) MXR_MASK_VAL(x, 11, 8) +#define MXR_GRP_CFG_FORMAT_MASK MXR_GRP_CFG_FORMAT_VAL(~0) +#define MXR_GRP_CFG_ALPHA_VAL(x) MXR_MASK_VAL(x, 7, 0) + +/* bits for MXR_GRAPHICn_WH */ +#define MXR_GRP_WH_H_SCALE(x) MXR_MASK_VAL(x, 28, 28) +#define MXR_GRP_WH_V_SCALE(x) MXR_MASK_VAL(x, 12, 12) +#define MXR_GRP_WH_WIDTH(x) MXR_MASK_VAL(x, 26, 16) +#define MXR_GRP_WH_HEIGHT(x) MXR_MASK_VAL(x, 10, 0) + +/* bits for MXR_GRAPHICn_SXY */ +#define MXR_GRP_SXY_SX(x) MXR_MASK_VAL(x, 26, 16) +#define MXR_GRP_SXY_SY(x) MXR_MASK_VAL(x, 10, 0) + +/* bits for MXR_GRAPHICn_DXY */ +#define MXR_GRP_DXY_DX(x) MXR_MASK_VAL(x, 26, 16) +#define MXR_GRP_DXY_DY(x) MXR_MASK_VAL(x, 10, 0) + +/* bits for MXR_INT_EN */ +#define MXR_INT_EN_VSYNC (1 << 11) +#define MXR_INT_EN_ALL (0x0f << 8) + +/* bit for MXR_INT_STATUS */ +#define MXR_INT_CLEAR_VSYNC (1 << 11) +#define MXR_INT_STATUS_VSYNC (1 << 0) + +/* bit for MXR_LAYER_CFG */ +#define MXR_LAYER_CFG_GRP1_VAL(x) MXR_MASK_VAL(x, 11, 8) +#define MXR_LAYER_CFG_GRP0_VAL(x) MXR_MASK_VAL(x, 7, 4) +#define MXR_LAYER_CFG_VP_VAL(x) MXR_MASK_VAL(x, 3, 0) + +#endif /* SAMSUNG_REGS_MIXER_H */ + diff --git a/drivers/gpu/drm/exynos/regs-vp.h b/drivers/gpu/drm/exynos/regs-vp.h new file mode 100644 index 000000000000..10b737af0a72 --- /dev/null +++ b/drivers/gpu/drm/exynos/regs-vp.h @@ -0,0 +1,91 @@ +/* + * + * Cloned from drivers/media/video/s5p-tv/regs-vp.h + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Video processor register header file for Samsung Mixer driver + * + * 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. + */ + +#ifndef SAMSUNG_REGS_VP_H +#define SAMSUNG_REGS_VP_H + +/* + * Register part + */ + +#define VP_ENABLE 0x0000 +#define VP_SRESET 0x0004 +#define VP_SHADOW_UPDATE 0x0008 +#define VP_FIELD_ID 0x000C +#define VP_MODE 0x0010 +#define VP_IMG_SIZE_Y 0x0014 +#define VP_IMG_SIZE_C 0x0018 +#define VP_PER_RATE_CTRL 0x001C +#define VP_TOP_Y_PTR 0x0028 +#define VP_BOT_Y_PTR 0x002C +#define VP_TOP_C_PTR 0x0030 +#define VP_BOT_C_PTR 0x0034 +#define VP_ENDIAN_MODE 0x03CC +#define VP_SRC_H_POSITION 0x0044 +#define VP_SRC_V_POSITION 0x0048 +#define VP_SRC_WIDTH 0x004C +#define VP_SRC_HEIGHT 0x0050 +#define VP_DST_H_POSITION 0x0054 +#define VP_DST_V_POSITION 0x0058 +#define VP_DST_WIDTH 0x005C +#define VP_DST_HEIGHT 0x0060 +#define VP_H_RATIO 0x0064 +#define VP_V_RATIO 0x0068 +#define VP_POLY8_Y0_LL 0x006C +#define VP_POLY4_Y0_LL 0x00EC +#define VP_POLY4_C0_LL 0x012C + +/* + * Bit definition part + */ + +/* generates mask for range of bits */ + +#define VP_MASK(high_bit, low_bit) \ + (((2 << ((high_bit) - (low_bit))) - 1) << (low_bit)) + +#define VP_MASK_VAL(val, high_bit, low_bit) \ + (((val) << (low_bit)) & VP_MASK(high_bit, low_bit)) + + /* VP_ENABLE */ +#define VP_ENABLE_ON (1 << 0) + +/* VP_SRESET */ +#define VP_SRESET_PROCESSING (1 << 0) + +/* VP_SHADOW_UPDATE */ +#define VP_SHADOW_UPDATE_ENABLE (1 << 0) + +/* VP_MODE */ +#define VP_MODE_NV12 (0 << 6) +#define VP_MODE_NV21 (1 << 6) +#define VP_MODE_LINE_SKIP (1 << 5) +#define VP_MODE_MEM_LINEAR (0 << 4) +#define VP_MODE_MEM_TILED (1 << 4) +#define VP_MODE_FMT_MASK (5 << 4) +#define VP_MODE_FIELD_ID_AUTO_TOGGLING (1 << 2) +#define VP_MODE_2D_IPC (1 << 1) + +/* VP_IMG_SIZE_Y */ +/* VP_IMG_SIZE_C */ +#define VP_IMG_HSIZE(x) VP_MASK_VAL(x, 29, 16) +#define VP_IMG_VSIZE(x) VP_MASK_VAL(x, 13, 0) + +/* VP_SRC_H_POSITION */ +#define VP_SRC_H_POSITION_VAL(x) VP_MASK_VAL(x, 14, 4) + +/* VP_ENDIAN_MODE */ +#define VP_ENDIAN_MODE_LITTLE (1 << 0) + +#endif /* SAMSUNG_REGS_VP_H */ |