[PATCH 1/2] DRM: add Freescale i.MX LCDC driver
Sascha Hauer
s.hauer at pengutronix.de
Fri May 18 08:27:11 EDT 2012
Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
drivers/gpu/drm/Kconfig | 2 +
drivers/gpu/drm/Makefile | 1 +
drivers/gpu/drm/imx/Kconfig | 18 +
drivers/gpu/drm/imx/Makefile | 8 +
drivers/gpu/drm/imx/imx-drm-core.c | 745 ++++++++++++++++++++++++++++
drivers/gpu/drm/imx/imx-fb.c | 179 +++++++
drivers/gpu/drm/imx/imx-fbdev.c | 275 ++++++++++
drivers/gpu/drm/imx/imx-gem.c | 343 +++++++++++++
drivers/gpu/drm/imx/imx-lcdc-crtc.c | 517 +++++++++++++++++++
drivers/gpu/drm/imx/imx-parallel-display.c | 228 +++++++++
10 files changed, 2316 insertions(+)
create mode 100644 drivers/gpu/drm/imx/Kconfig
create mode 100644 drivers/gpu/drm/imx/Makefile
create mode 100644 drivers/gpu/drm/imx/imx-drm-core.c
create mode 100644 drivers/gpu/drm/imx/imx-fb.c
create mode 100644 drivers/gpu/drm/imx/imx-fbdev.c
create mode 100644 drivers/gpu/drm/imx/imx-gem.c
create mode 100644 drivers/gpu/drm/imx/imx-lcdc-crtc.c
create mode 100644 drivers/gpu/drm/imx/imx-parallel-display.c
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index e354bc0..759502c 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -186,3 +186,5 @@ source "drivers/gpu/drm/vmwgfx/Kconfig"
source "drivers/gpu/drm/gma500/Kconfig"
source "drivers/gpu/drm/udl/Kconfig"
+
+source "drivers/gpu/drm/imx/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index c20da5b..6569d8d 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -42,4 +42,5 @@ obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/
obj-$(CONFIG_DRM_EXYNOS) +=exynos/
obj-$(CONFIG_DRM_GMA500) += gma500/
obj-$(CONFIG_DRM_UDL) += udl/
+obj-$(CONFIG_DRM_IMX) += imx/
obj-y += i2c/
diff --git a/drivers/gpu/drm/imx/Kconfig b/drivers/gpu/drm/imx/Kconfig
new file mode 100644
index 0000000..5fc3a44
--- /dev/null
+++ b/drivers/gpu/drm/imx/Kconfig
@@ -0,0 +1,18 @@
+config DRM_IMX
+ tristate "DRM Support for Freescale i.MX"
+ select DRM_KMS_HELPER
+ depends on DRM && ARCH_MXC
+
+config DRM_IMX_FB_HELPER
+ tristate "provide legacy framebuffer /dev/fb0"
+ depends on DRM_IMX
+
+config DRM_IMX_LCDC
+ tristate "DRM Support for Freescale i.MX1 and i.MX2"
+ depends on DRM_IMX
+ help
+ Choose this if you have a i.MX1, i.MX21, i.MX25 or i.MX27 processor.
+
+config DRM_IMX_PARALLEL_DISPLAY
+ tristate "Support for parallel displays"
+ depends on DRM_IMX
diff --git a/drivers/gpu/drm/imx/Makefile b/drivers/gpu/drm/imx/Makefile
new file mode 100644
index 0000000..0f7c038
--- /dev/null
+++ b/drivers/gpu/drm/imx/Makefile
@@ -0,0 +1,8 @@
+
+imxdrm-objs := imx-drm-core.o imx-fb.o imx-gem.o
+
+obj-$(CONFIG_DRM_IMX) += imxdrm.o
+
+obj-$(CONFIG_DRM_IMX_PARALLEL_DISPLAY) += imx-parallel-display.o
+obj-$(CONFIG_DRM_IMX_LCDC) += imx-lcdc-crtc.o
+obj-$(CONFIG_DRM_IMX_FB_HELPER) += imx-fbdev.o
diff --git a/drivers/gpu/drm/imx/imx-drm-core.c b/drivers/gpu/drm/imx/imx-drm-core.c
new file mode 100644
index 0000000..29f5f10
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-drm-core.c
@@ -0,0 +1,745 @@
+/*
+ * simple drm driver
+ *
+ * Copyright (C) 2011 Sascha Hauer, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <linux/fb.h>
+#include <asm/fb.h>
+#include <linux/module.h>
+
+#include "imx-drm.h"
+
+#define MAX_CRTC 4
+
+struct imx_drm_device {
+ struct drm_device *drm;
+ struct device *dev;
+ struct list_head crtc_list;
+ struct list_head encoder_list;
+ struct list_head connector_list;
+ struct mutex mutex;
+ int references;
+};
+
+struct imx_drm_crtc {
+ struct drm_crtc *crtc;
+ struct list_head list;
+ struct imx_drm_device *imxdrm;
+ int pipe;
+ struct drm_crtc_helper_funcs crtc_helper_funcs;
+ struct drm_crtc_funcs crtc_funcs;
+ struct imx_drm_crtc_helper_funcs imx_drm_helper_funcs;
+ struct module *owner;
+};
+
+struct imx_drm_encoder {
+ struct drm_encoder *encoder;
+ struct list_head list;
+ struct module *owner;
+};
+
+struct imx_drm_connector {
+ struct drm_connector *connector;
+ struct list_head list;
+ struct module *owner;
+};
+
+static int imx_drm_driver_firstopen(struct drm_device *drm)
+{
+ if (!imx_drm_device_get())
+ return -EINVAL;
+
+ return 0;
+}
+
+static void imx_drm_driver_lastclose(struct drm_device *drm)
+{
+ imx_drm_device_put();
+}
+
+static int imx_drm_driver_unload(struct drm_device *drm)
+{
+ struct imx_drm_device *imxdrm = drm->dev_private;
+
+ drm_mode_config_cleanup(imxdrm->drm);
+ drm_kms_helper_poll_fini(imxdrm->drm);
+
+ return 0;
+}
+
+/*
+ * We don't care at all for crtc numbers, but the core expects the
+ * crtcs to be numbered
+ */
+static struct imx_drm_crtc *imx_drm_crtc_by_num(struct imx_drm_device *imxdrm,
+ int num)
+{
+ struct imx_drm_crtc *imx_drm_crtc;
+
+ list_for_each_entry(imx_drm_crtc, &imxdrm->crtc_list, list)
+ if (imx_drm_crtc->pipe == num)
+ return imx_drm_crtc;
+ return NULL;
+}
+
+int imx_drm_crtc_vblank_get(struct imx_drm_crtc *imx_drm_crtc)
+{
+ return drm_vblank_get(imx_drm_crtc->imxdrm->drm, imx_drm_crtc->pipe);
+}
+EXPORT_SYMBOL_GPL(imx_drm_crtc_vblank_get);
+
+void imx_drm_crtc_vblank_put(struct imx_drm_crtc *imx_drm_crtc)
+{
+ drm_vblank_put(imx_drm_crtc->imxdrm->drm, imx_drm_crtc->pipe);
+}
+EXPORT_SYMBOL_GPL(imx_drm_crtc_vblank_put);
+
+void imx_drm_handle_vblank(struct imx_drm_crtc *imx_drm_crtc)
+{
+ drm_handle_vblank(imx_drm_crtc->imxdrm->drm, imx_drm_crtc->pipe);
+}
+EXPORT_SYMBOL_GPL(imx_drm_handle_vblank);
+
+static int imx_drm_enable_vblank(struct drm_device *drm, int crtc)
+{
+ struct imx_drm_device *imxdrm = drm->dev_private;
+ struct imx_drm_crtc *imx_drm_crtc;
+ int ret;
+
+ imx_drm_crtc = imx_drm_crtc_by_num(imxdrm, crtc);
+ if (!imx_drm_crtc)
+ return -EINVAL;
+
+ if (!imx_drm_crtc->imx_drm_helper_funcs.enable_vblank)
+ return -ENOSYS;
+
+ ret = imx_drm_crtc->imx_drm_helper_funcs.enable_vblank(imx_drm_crtc->crtc);
+ return ret;
+}
+
+static void imx_drm_disable_vblank(struct drm_device *drm, int crtc)
+{
+ struct imx_drm_device *imxdrm = drm->dev_private;
+ struct imx_drm_crtc *imx_drm_crtc;
+
+ imx_drm_crtc = imx_drm_crtc_by_num(imxdrm, crtc);
+ if (!imx_drm_crtc)
+ return;
+
+ if (!imx_drm_crtc->imx_drm_helper_funcs.disable_vblank)
+ return;
+
+ imx_drm_crtc->imx_drm_helper_funcs.disable_vblank(imx_drm_crtc->crtc);
+}
+
+static struct vm_operations_struct imx_drm_gem_vm_ops = {
+ .fault = imx_drm_gem_fault,
+ .open = drm_gem_vm_open,
+ .close = drm_gem_vm_close,
+};
+
+static const struct file_operations imx_drm_driver_fops = {
+ .owner = THIS_MODULE,
+ .open = drm_open,
+ .release = drm_release,
+ .unlocked_ioctl = drm_ioctl,
+ .mmap = imx_drm_gem_mmap,
+ .poll = drm_poll,
+ .fasync = drm_fasync,
+ .read = drm_read,
+ .llseek = noop_llseek,
+};
+
+static struct imx_drm_device *imx_drm_device;
+
+static struct imx_drm_device *__imx_drm_device(void)
+{
+ return imx_drm_device;
+}
+
+struct drm_device *imx_drm_device_get(void)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+ struct imx_drm_encoder *enc;
+ struct imx_drm_connector *con;
+ struct imx_drm_crtc *crtc;
+
+ mutex_lock(&imxdrm->mutex);
+
+ list_for_each_entry(enc, &imxdrm->encoder_list, list) {
+ if (!try_module_get(enc->owner)) {
+ dev_err(imxdrm->dev, "could not get module %s\n",
+ module_name(enc->owner));
+ goto unwind_enc;
+ }
+ }
+
+ list_for_each_entry(con, &imxdrm->connector_list, list) {
+ if (!try_module_get(con->owner)) {
+ dev_err(imxdrm->dev, "could not get module %s\n",
+ module_name(con->owner));
+ goto unwind_con;
+ }
+ }
+
+ list_for_each_entry(crtc, &imxdrm->crtc_list, list) {
+ if (!try_module_get(crtc->owner)) {
+ dev_err(imxdrm->dev, "could not get module %s\n",
+ module_name(crtc->owner));
+ goto unwind_crtc;
+ }
+ }
+
+ imxdrm->references++;
+
+ mutex_unlock(&imxdrm->mutex);
+
+ return imx_drm_device->drm;
+
+unwind_crtc:
+ list_for_each_entry_continue_reverse(crtc, &imxdrm->crtc_list, list)
+ module_put(crtc->owner);
+unwind_con:
+ list_for_each_entry_continue_reverse(con, &imxdrm->connector_list, list)
+ module_put(con->owner);
+unwind_enc:
+ list_for_each_entry_continue_reverse(enc, &imxdrm->encoder_list, list)
+ module_put(enc->owner);
+
+ mutex_unlock(&imxdrm->mutex);
+
+ return NULL;
+
+}
+EXPORT_SYMBOL_GPL(imx_drm_device_get);
+
+void imx_drm_device_put(void)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+ struct imx_drm_encoder *enc;
+ struct imx_drm_connector *con;
+ struct imx_drm_crtc *crtc;
+
+ mutex_lock(&imxdrm->mutex);
+
+ list_for_each_entry(crtc, &imxdrm->crtc_list, list)
+ module_put(crtc->owner);
+
+ list_for_each_entry(con, &imxdrm->connector_list, list)
+ module_put(con->owner);
+
+ list_for_each_entry(enc, &imxdrm->encoder_list, list)
+ module_put(enc->owner);
+
+ imxdrm->references--;
+
+ mutex_unlock(&imxdrm->mutex);
+}
+EXPORT_SYMBOL_GPL(imx_drm_device_put);
+
+static int drm_mode_group_reinit(struct drm_device *dev)
+{
+ struct drm_mode_group *group = &dev->primary->mode_group;
+ uint32_t *id_list = group->id_list;
+ int ret;
+
+ ret = drm_mode_group_init_legacy_group(dev, group);
+ if (ret < 0)
+ return ret;
+
+ kfree(id_list);
+ return 0;
+}
+
+/*
+ * register an encoder to the drm core
+ */
+static int imx_drm_encoder_register(struct imx_drm_encoder *imx_drm_encoder)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+
+ drm_encoder_init(imxdrm->drm, imx_drm_encoder->encoder,
+ imx_drm_encoder->encoder->funcs,
+ DRM_MODE_ENCODER_TMDS);
+
+ drm_mode_group_reinit(imxdrm->drm);
+
+ return 0;
+}
+
+/*
+ * unregister an encoder from the drm core
+ */
+static void imx_drm_encoder_unregister(struct imx_drm_encoder *imx_drm_encoder)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+
+ drm_encoder_cleanup(imx_drm_encoder->encoder);
+
+ drm_mode_group_reinit(imxdrm->drm);
+}
+
+/*
+ * register a connector to the drm core
+ */
+static int imx_drm_connector_register(struct imx_drm_connector *imx_drm_connector)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+ int ret;
+
+ drm_connector_init(imxdrm->drm, imx_drm_connector->connector,
+ imx_drm_connector->connector->funcs,
+ DRM_MODE_CONNECTOR_VGA);
+ drm_mode_group_reinit(imxdrm->drm);
+ ret = drm_sysfs_connector_add(imx_drm_connector->connector);
+ if (ret)
+ goto err;
+
+ return 0;
+err:
+
+ return ret;
+}
+
+/*
+ * unregister a connector from the drm core
+ */
+static void imx_drm_connector_unregister(struct imx_drm_connector *imx_drm_connector)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+
+ drm_sysfs_connector_remove(imx_drm_connector->connector);
+ drm_connector_cleanup(imx_drm_connector->connector);
+
+ drm_mode_group_reinit(imxdrm->drm);
+}
+
+/*
+ * register a crtc to the drm core
+ */
+static int imx_drm_crtc_register(struct imx_drm_crtc *imx_drm_crtc)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+ int ret;
+
+ drm_crtc_init(imxdrm->drm, imx_drm_crtc->crtc, &imx_drm_crtc->crtc_funcs);
+ ret = drm_mode_crtc_set_gamma_size(imx_drm_crtc->crtc, 256);
+ if (ret)
+ return ret;
+
+ drm_crtc_helper_add(imx_drm_crtc->crtc, &imx_drm_crtc->crtc_helper_funcs);
+
+ return 0;
+}
+
+/*
+ * Called by the CRTC driver when all CRTCs are registered. This
+ * puts all the pieces together and initializes the driver.
+ * Once this is called no more CRTCs can be registered since
+ * the drm core has hardcoded the number of crtcs in several
+ * places.
+ */
+static int imx_drm_driver_load(struct drm_device *drm, unsigned long flags)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+ int ret;
+
+ imxdrm->drm = drm;
+
+ drm->dev_private = imxdrm;
+
+ /*
+ * enable drm irq mode.
+ * - with irq_enabled = 1, we can use the vblank feature.
+ *
+ * P.S. note that we wouldn't use drm irq handler but
+ * just specific driver own one instead because
+ * drm framework supports only one irq handler and
+ * drivers can well take care of their interrupts
+ */
+ drm->irq_enabled = 1;
+
+ drm_mode_config_init(drm);
+ imx_drm_mode_config_init(drm);
+
+ mutex_lock(&imxdrm->mutex);
+
+ drm_kms_helper_poll_init(imxdrm->drm);
+
+ /* setup the grouping for the legacy output */
+ ret = drm_mode_group_init_legacy_group(imxdrm->drm,
+ &imxdrm->drm->primary->mode_group);
+ if (ret)
+ goto err_init;
+
+ ret = drm_vblank_init(imxdrm->drm, MAX_CRTC);
+ if (ret)
+ goto err_init;
+
+ /*
+ * 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)
+ */
+ imxdrm->drm->vblank_disable_allowed = 1;
+
+ ret = 0;
+
+err_init:
+ mutex_unlock(&imxdrm->mutex);
+
+ return ret;
+}
+
+/*
+ * imx_drm_add_crtc - add a new crtc
+ *
+ * The return value if !NULL is a cookie for the caller to pass to
+ * imx_drm_remove_crtc later.
+ */
+int imx_drm_add_crtc(struct drm_crtc *crtc,
+ struct imx_drm_crtc **new_crtc,
+ const struct drm_crtc_funcs *crtc_funcs,
+ const struct drm_crtc_helper_funcs *crtc_helper_funcs,
+ const struct imx_drm_crtc_helper_funcs *imx_drm_helper_funcs,
+ struct module *owner)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+ struct imx_drm_crtc *imx_drm_crtc;
+ int ret;
+
+ mutex_lock(&imxdrm->mutex);
+
+ if (imxdrm->references) {
+ ret = -EBUSY;
+ goto err_busy;
+ }
+
+ imx_drm_crtc = kzalloc(sizeof(*imx_drm_crtc), GFP_KERNEL);
+ if (!imx_drm_crtc) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ imx_drm_crtc->crtc_funcs = *crtc_funcs;
+ imx_drm_crtc->crtc_helper_funcs = *crtc_helper_funcs;
+ imx_drm_crtc->imx_drm_helper_funcs = *imx_drm_helper_funcs;
+
+ WARN_ON(crtc_funcs->set_config);
+ WARN_ON(crtc_funcs->destroy);
+
+ imx_drm_crtc->crtc_funcs.set_config = drm_crtc_helper_set_config;
+ imx_drm_crtc->crtc_funcs.destroy = drm_crtc_cleanup;
+
+ imx_drm_crtc->crtc = crtc;
+ imx_drm_crtc->imxdrm = imxdrm;
+
+ imx_drm_crtc->owner = owner;
+
+ list_add_tail(&imx_drm_crtc->list, &imxdrm->crtc_list);
+
+ *new_crtc = imx_drm_crtc;
+
+ ret = imx_drm_crtc_register(imx_drm_crtc);
+ if (ret)
+ goto err_register;
+
+ mutex_unlock(&imxdrm->mutex);
+
+ return 0;
+
+err_register:
+ kfree(imx_drm_crtc);
+err_alloc:
+err_busy:
+ mutex_unlock(&imxdrm->mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(imx_drm_add_crtc);
+
+/*
+ * imx_drm_remove_crtc - remove a crtc
+ */
+int imx_drm_remove_crtc(struct imx_drm_crtc *imx_drm_crtc)
+{
+ struct imx_drm_device *imxdrm = imx_drm_crtc->imxdrm;
+
+ mutex_lock(&imxdrm->mutex);
+
+ drm_crtc_cleanup(imx_drm_crtc->crtc);
+
+ list_del(&imx_drm_crtc->list);
+
+ mutex_unlock(&imxdrm->mutex);
+
+ kfree(imx_drm_crtc);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imx_drm_remove_crtc);
+
+/*
+ * imx_drm_add_encoder - add a new encoder
+ */
+int imx_drm_add_encoder(struct drm_encoder *encoder,
+ struct imx_drm_encoder **newenc, struct module *owner)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+ struct imx_drm_encoder *imx_drm_encoder;
+ int ret;
+
+ mutex_lock(&imxdrm->mutex);
+
+ if (imxdrm->references) {
+ ret = -EBUSY;
+ goto err_busy;
+ }
+
+ imx_drm_encoder = kzalloc(sizeof(struct imx_drm_encoder), GFP_KERNEL);
+ if (!imx_drm_encoder) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ imx_drm_encoder->encoder = encoder;
+ imx_drm_encoder->owner = owner;
+
+ ret = imx_drm_encoder_register(imx_drm_encoder);
+ if (ret) {
+ kfree(imx_drm_encoder);
+ ret = -ENOMEM;
+ goto err_register;
+ }
+
+ list_add_tail(&imx_drm_encoder->list, &imxdrm->encoder_list);
+
+ *newenc = imx_drm_encoder;
+
+ mutex_unlock(&imxdrm->mutex);
+
+ return 0;
+
+err_register:
+ kfree(imx_drm_encoder);
+err_alloc:
+err_busy:
+ mutex_unlock(&imxdrm->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(imx_drm_add_encoder);
+
+/*
+ * imx_drm_remove_encoder - remove an encoder
+ */
+int imx_drm_remove_encoder(struct imx_drm_encoder *imx_drm_encoder)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+
+ mutex_lock(&imxdrm->mutex);
+
+ imx_drm_encoder_unregister(imx_drm_encoder);
+
+ list_del(&imx_drm_encoder->list);
+
+ mutex_unlock(&imxdrm->mutex);
+
+ kfree(imx_drm_encoder);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imx_drm_remove_encoder);
+
+/*
+ * imx_drm_add_connector - add a connector
+ */
+int imx_drm_add_connector(struct drm_connector *connector,
+ struct imx_drm_connector **new_con,
+ struct module *owner)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+ struct imx_drm_connector *imx_drm_connector;
+ int ret;
+
+ mutex_lock(&imxdrm->mutex);
+
+ if (imxdrm->references) {
+ ret = -EBUSY;
+ goto err_busy;
+ }
+
+ imx_drm_connector = kzalloc(sizeof(struct imx_drm_connector), GFP_KERNEL);
+ if (!imx_drm_connector) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ imx_drm_connector->connector = connector;
+ imx_drm_connector->owner = owner;
+
+ ret = imx_drm_connector_register(imx_drm_connector);
+ if (ret)
+ goto err_register;
+
+ list_add_tail(&imx_drm_connector->list, &imxdrm->connector_list);
+
+ *new_con = imx_drm_connector;
+
+ mutex_unlock(&imxdrm->mutex);
+
+ return 0;
+
+err_register:
+ kfree(imx_drm_connector);
+err_alloc:
+err_busy:
+ mutex_unlock(&imxdrm->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(imx_drm_add_connector);
+
+/*
+ * imx_drm_remove_connector - remove a connector
+ */
+int imx_drm_remove_connector(struct imx_drm_connector *imx_drm_connector)
+{
+ struct imx_drm_device *imxdrm = __imx_drm_device();
+
+ mutex_lock(&imxdrm->mutex);
+
+ imx_drm_connector_unregister(imx_drm_connector);
+
+ list_del(&imx_drm_connector->list);
+
+ mutex_unlock(&imxdrm->mutex);
+
+ kfree(imx_drm_connector);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(imx_drm_remove_connector);
+
+static struct drm_ioctl_desc imx_drm_ioctls[] = {
+ /* none so far */
+};
+
+static struct drm_driver imx_drm_driver = {
+ .driver_features = DRIVER_MODESET | DRIVER_GEM,
+ .load = imx_drm_driver_load,
+ .unload = imx_drm_driver_unload,
+ .firstopen = imx_drm_driver_firstopen,
+ .lastclose = imx_drm_driver_lastclose,
+ .gem_free_object = imx_drm_gem_free_object,
+ .gem_vm_ops = &imx_drm_gem_vm_ops,
+ .dumb_create = imx_drm_gem_dumb_create,
+ .dumb_map_offset = imx_drm_gem_dumb_map_offset,
+ .dumb_destroy = imx_drm_gem_dumb_destroy,
+
+ .get_vblank_counter = drm_vblank_count,
+ .enable_vblank = imx_drm_enable_vblank,
+ .disable_vblank = imx_drm_disable_vblank,
+ .reclaim_buffers = drm_core_reclaim_buffers,
+ .ioctls = imx_drm_ioctls,
+ .num_ioctls = ARRAY_SIZE(imx_drm_ioctls),
+ .fops = &imx_drm_driver_fops,
+ .name = "imx-drm",
+ .desc = "i.MX DRM graphics",
+ .date = "20120507",
+ .major = 1,
+ .minor = 0,
+ .patchlevel = 0,
+};
+
+static int imx_drm_platform_probe(struct platform_device *pdev)
+{
+ imx_drm_device->dev = &pdev->dev;
+
+ return drm_platform_init(&imx_drm_driver, pdev);
+}
+
+static int imx_drm_platform_remove(struct platform_device *pdev)
+{
+ drm_platform_exit(&imx_drm_driver, pdev);
+
+ return 0;
+}
+
+static struct platform_driver imx_drm_pdrv = {
+ .probe = imx_drm_platform_probe,
+ .remove = __devexit_p(imx_drm_platform_remove),
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "imx-drm",
+ },
+};
+
+static struct platform_device *imx_drm_pdev;
+
+static int __init imx_drm_init(void)
+{
+ int ret;
+
+ imx_drm_device = kzalloc(sizeof(*imx_drm_device), GFP_KERNEL);
+ if (!imx_drm_device)
+ return -ENOMEM;
+
+ mutex_init(&imx_drm_device->mutex);
+ INIT_LIST_HEAD(&imx_drm_device->crtc_list);
+ INIT_LIST_HEAD(&imx_drm_device->connector_list);
+ INIT_LIST_HEAD(&imx_drm_device->encoder_list);
+
+ imx_drm_pdev = platform_device_register_simple("imx-drm", -1, NULL, 0);
+ if (!imx_drm_pdev) {
+ ret = -EINVAL;
+ goto err_pdev;
+ }
+
+ imx_drm_pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32),
+
+ ret = platform_driver_register(&imx_drm_pdrv);
+ if (ret)
+ goto err_pdrv;
+
+ return 0;
+
+err_pdev:
+ kfree(imx_drm_device);
+err_pdrv:
+ platform_device_unregister(imx_drm_pdev);
+
+ return ret;
+}
+
+static void __exit imx_drm_exit(void)
+{
+ DRM_DEBUG_DRIVER("%s\n", __FILE__);
+
+ platform_device_unregister(imx_drm_pdev);
+ platform_driver_unregister(&imx_drm_pdrv);
+
+ kfree(imx_drm_device);
+}
+
+module_init(imx_drm_init);
+module_exit(imx_drm_exit);
+
+MODULE_AUTHOR("Sascha Hauer <s.hauer at pengutronix.de>");
+MODULE_DESCRIPTION("i.MX drm driver core");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/imx-fb.c b/drivers/gpu/drm/imx/imx-fb.c
new file mode 100644
index 0000000..5a08c86
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-fb.c
@@ -0,0 +1,179 @@
+/*
+ * i.MX drm driver
+ *
+ * Copyright (C) 2012 Sascha Hauer, Pengutronix
+ *
+ * Based on Samsung Exynos code
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "imx-drm.h"
+
+#define to_imx_drm_fb(x) container_of(x, struct imx_drm_fb, fb)
+
+/*
+ * imx specific framebuffer structure.
+ *
+ * @fb: drm framebuffer obejct.
+ * @imx_drm_gem_obj: drm ec specific gem object containing a gem object.
+ * @entry: pointer to ec drm buffer entry object.
+ * - containing only the information to physically continuous memory
+ * region allocated at default framebuffer creation.
+ */
+struct imx_drm_fb {
+ struct drm_framebuffer fb;
+ struct imx_drm_gem_obj *imx_drm_gem_obj;
+ struct imx_drm_buf_entry *entry;
+};
+
+static void imx_drm_fb_destroy(struct drm_framebuffer *fb)
+{
+ struct imx_drm_fb *imx_drm_fb = to_imx_drm_fb(fb);
+
+ drm_framebuffer_cleanup(fb);
+
+ /*
+ * default framebuffer has no gem object so
+ * a buffer of the default framebuffer should be released at here.
+ */
+ if (!imx_drm_fb->imx_drm_gem_obj && imx_drm_fb->entry)
+ imx_drm_buf_destroy(fb->dev, imx_drm_fb->entry);
+
+ kfree(imx_drm_fb);
+}
+
+static int imx_drm_fb_create_handle(struct drm_framebuffer *fb,
+ struct drm_file *file_priv, unsigned int *handle)
+{
+ struct imx_drm_fb *imx_drm_fb = to_imx_drm_fb(fb);
+
+ return drm_gem_handle_create(file_priv,
+ &imx_drm_fb->imx_drm_gem_obj->base, handle);
+}
+
+static struct drm_framebuffer_funcs imx_drm_fb_funcs = {
+ .destroy = imx_drm_fb_destroy,
+ .create_handle = imx_drm_fb_create_handle,
+};
+
+static struct drm_framebuffer *imx_drm_fb_create(struct drm_device *dev,
+ struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd)
+{
+ struct imx_drm_fb *imx_drm_fb;
+ struct drm_framebuffer *fb;
+ struct drm_gem_object *obj;
+ unsigned int size;
+ int ret;
+ u32 bpp, depth;
+
+ drm_fb_get_bpp_depth(mode_cmd->pixel_format, &depth, &bpp);
+
+ mode_cmd->pitches[0] = max(mode_cmd->pitches[0],
+ mode_cmd->width * (bpp >> 3));
+
+ dev_dbg(dev->dev, "drm fb create(%dx%d)\n",
+ mode_cmd->width, mode_cmd->height);
+
+ imx_drm_fb = kzalloc(sizeof(*imx_drm_fb), GFP_KERNEL);
+ if (!imx_drm_fb) {
+ dev_err(dev->dev, "failed to allocate drm framebuffer.\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ fb = &imx_drm_fb->fb;
+ ret = drm_framebuffer_init(dev, fb, &imx_drm_fb_funcs);
+ if (ret) {
+ dev_err(dev->dev, "failed to initialize framebuffer.\n");
+ goto err_init;
+ }
+
+ dev_dbg(dev->dev, "create: fb id: %d\n", fb->base.id);
+
+ size = mode_cmd->pitches[0] * mode_cmd->height;
+
+ /*
+ * without file_priv we are called from imx_drm_fbdev_create in which
+ * case we only create a framebuffer without a handle.
+ */
+ if (!file_priv) {
+ struct imx_drm_buf_entry *entry;
+
+ entry = imx_drm_buf_create(dev, size);
+ if (IS_ERR(entry)) {
+ ret = PTR_ERR(entry);
+ goto err_buffer;
+ }
+
+ imx_drm_fb->entry = entry;
+ } else {
+ obj = drm_gem_object_lookup(dev, file_priv, mode_cmd->handles[0]);
+ if (!obj) {
+ ret = -EINVAL;
+ goto err_buffer;
+ }
+
+ imx_drm_fb->imx_drm_gem_obj = to_imx_drm_gem_obj(obj);
+
+ drm_gem_object_unreference_unlocked(obj);
+
+ imx_drm_fb->entry = imx_drm_fb->imx_drm_gem_obj->entry;
+ }
+
+ drm_helper_mode_fill_fb_struct(fb, mode_cmd);
+
+ return fb;
+
+err_buffer:
+ drm_framebuffer_cleanup(fb);
+
+err_init:
+ kfree(imx_drm_fb);
+
+ return ERR_PTR(ret);
+}
+
+struct imx_drm_buf_entry *imx_drm_fb_get_buf(struct drm_framebuffer *fb)
+{
+ struct imx_drm_fb *imx_drm_fb = to_imx_drm_fb(fb);
+ struct imx_drm_buf_entry *entry;
+
+ entry = imx_drm_fb->entry;
+
+ return entry;
+}
+EXPORT_SYMBOL_GPL(imx_drm_fb_get_buf);
+
+static struct drm_mode_config_funcs imx_drm_mode_config_funcs = {
+ .fb_create = imx_drm_fb_create,
+};
+
+void imx_drm_mode_config_init(struct drm_device *dev)
+{
+ dev->mode_config.min_width = 64;
+ dev->mode_config.min_height = 64;
+
+ /*
+ * set max width and height as default value(4096x4096).
+ * this value would be used to check framebuffer size limitation
+ * at drm_mode_addfb().
+ */
+ dev->mode_config.max_width = 4096;
+ dev->mode_config.max_height = 4096;
+
+ dev->mode_config.funcs = &imx_drm_mode_config_funcs;
+}
diff --git a/drivers/gpu/drm/imx/imx-fbdev.c b/drivers/gpu/drm/imx/imx-fbdev.c
new file mode 100644
index 0000000..f038797
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-fbdev.c
@@ -0,0 +1,275 @@
+/*
+ * i.MX drm driver
+ *
+ * Copyright (C) 2012 Sascha Hauer, Pengutronix
+ *
+ * Based on Samsung Exynos code
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "imx-drm.h"
+
+#define MAX_CONNECTOR 4
+#define PREFERRED_BPP 16
+
+static struct fb_ops imx_drm_fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_check_var = drm_fb_helper_check_var,
+ .fb_set_par = drm_fb_helper_set_par,
+ .fb_blank = drm_fb_helper_blank,
+ .fb_pan_display = drm_fb_helper_pan_display,
+ .fb_setcmap = drm_fb_helper_setcmap,
+};
+
+static int imx_drm_fbdev_update(struct drm_fb_helper *helper,
+ struct drm_framebuffer *fb,
+ unsigned int fb_width,
+ unsigned int fb_height)
+{
+ struct fb_info *fbi = helper->fbdev;
+ struct drm_device *drm = helper->dev;
+ struct imx_drm_buf_entry *entry;
+ unsigned int size = fb_width * fb_height * (fb->bits_per_pixel >> 3);
+ unsigned long offset;
+
+ drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth);
+ drm_fb_helper_fill_var(fbi, helper, fb_width, fb_height);
+
+ entry = imx_drm_fb_get_buf(fb);
+ if (!entry) {
+ dev_dbg(drm->dev, "entry is null.\n");
+ return -EFAULT;
+ }
+
+ offset = fbi->var.xoffset * (fb->bits_per_pixel >> 3);
+ offset += fbi->var.yoffset * fb->pitches[0];
+
+ drm->mode_config.fb_base = entry->paddr;
+ fbi->screen_base = entry->vaddr + offset;
+ fbi->fix.smem_start = entry->paddr + offset;
+ fbi->screen_size = size;
+ fbi->fix.smem_len = size;
+ fbi->flags |= FBINFO_CAN_FORCE_OUTPUT;
+
+ return 0;
+}
+
+static int imx_drm_fbdev_create(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ struct drm_device *drm = helper->dev;
+ struct fb_info *fbi;
+ struct drm_framebuffer *fb;
+ struct drm_mode_fb_cmd2 mode_cmd = { 0 };
+ struct platform_device *pdev = drm->platformdev;
+ int ret;
+
+ dev_dbg(drm->dev, "surface width(%d), height(%d) and bpp(%d\n",
+ sizes->surface_width, sizes->surface_height,
+ sizes->surface_bpp);
+
+ mode_cmd.width = sizes->surface_width;
+ mode_cmd.height = sizes->surface_height;
+ mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
+ sizes->surface_depth);
+
+ mutex_lock(&drm->struct_mutex);
+
+ fbi = framebuffer_alloc(0, &pdev->dev);
+ if (!fbi) {
+ ret = -ENOMEM;
+ goto err_fb_alloc;
+ }
+
+ fb = drm->mode_config.funcs->fb_create(drm, NULL, &mode_cmd);
+ if (IS_ERR(fb)) {
+ dev_err(drm->dev, "failed to create drm framebuffer.\n");
+ ret = PTR_ERR(fb);
+ goto err_fb_create;
+ }
+
+ helper->fb = fb;
+ helper->fbdev = fbi;
+
+ fbi->par = helper;
+ fbi->flags = FBINFO_FLAG_DEFAULT;
+ fbi->fbops = &imx_drm_fb_ops;
+
+ ret = fb_alloc_cmap(&fbi->cmap, 256, 0);
+ if (ret)
+ goto err_alloc_cmap;
+
+ ret = imx_drm_fbdev_update(helper, helper->fb, sizes->fb_width,
+ sizes->fb_height);
+ if (ret)
+ goto err_fbdev_update;
+
+ mutex_unlock(&drm->struct_mutex);
+
+ return 0;
+
+err_fbdev_update:
+ fb_dealloc_cmap(&fbi->cmap);
+
+err_alloc_cmap:
+ fb->funcs->destroy(fb);
+
+err_fb_create:
+ framebuffer_release(fbi);
+
+err_fb_alloc:
+ mutex_unlock(&drm->struct_mutex);
+
+ return ret;
+}
+
+static int imx_drm_fbdev_probe(struct drm_fb_helper *helper,
+ struct drm_fb_helper_surface_size *sizes)
+{
+ int ret;
+
+ BUG_ON(helper->fb);
+
+ ret = imx_drm_fbdev_create(helper, sizes);
+ if (ret) {
+ dev_err(helper->dev->dev, "creating fbdev failed with %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * fb_helper expects a value more than 1 if succeed
+ * because register_framebuffer() should be called.
+ */
+ return 1;
+}
+
+static struct drm_fb_helper_funcs imx_drm_fb_helper_funcs = {
+ .fb_probe = imx_drm_fbdev_probe,
+};
+
+static struct drm_fb_helper *imx_drm_fbdev_init(struct drm_device *drm,
+ int preferred_bpp)
+{
+ struct drm_fb_helper *helper;
+ unsigned int num_crtc;
+ int ret;
+
+ helper = kzalloc(sizeof(*helper), GFP_KERNEL);
+ if (!helper)
+ return NULL;
+
+ helper->funcs = &imx_drm_fb_helper_funcs;
+
+ num_crtc = drm->mode_config.num_crtc;
+
+ ret = drm_fb_helper_init(drm, helper, num_crtc, MAX_CONNECTOR);
+ if (ret) {
+ dev_err(drm->dev, "initializing drm fb helper failed with %d\n",
+ ret);
+ goto err_init;
+ }
+
+ ret = drm_fb_helper_single_add_all_connectors(helper);
+ if (ret) {
+ dev_err(drm->dev, "registering drm_fb_helper_connector failed with %d\n",
+ ret);
+ goto err_setup;
+
+ }
+
+ ret = drm_fb_helper_initial_config(helper, preferred_bpp);
+ if (ret) {
+ dev_err(drm->dev, "initial config failed with %d\n", ret);
+ goto err_setup;
+ }
+
+ return helper;
+
+err_setup:
+ drm_fb_helper_fini(helper);
+
+err_init:
+ kfree(helper);
+
+ return NULL;
+}
+
+static void imx_drm_fbdev_fini(struct drm_fb_helper *helper)
+{
+ struct imx_drm_buf_entry *entry;
+
+ if (helper->fbdev) {
+ struct fb_info *info;
+ int ret;
+
+ info = helper->fbdev;
+ ret = unregister_framebuffer(info);
+ if (ret)
+ dev_err(helper->dev->dev, "unregister_framebuffer failed with %d\n",
+ ret);
+
+ if (info->cmap.len)
+ fb_dealloc_cmap(&info->cmap);
+
+ entry = imx_drm_fb_get_buf(helper->fb);
+
+ imx_drm_buf_destroy(helper->dev, entry);
+
+ framebuffer_release(info);
+ }
+
+ drm_fb_helper_fini(helper);
+
+ kfree(helper);
+}
+
+static struct drm_fb_helper *imx_fb_helper;
+
+static int __init imx_fb_helper_init(void)
+{
+ struct drm_device *drm = imx_drm_device_get();
+ int ret;
+
+ if (!drm)
+ return -EINVAL;
+
+ imx_fb_helper = imx_drm_fbdev_init(drm, PREFERRED_BPP);
+ if (!imx_fb_helper) {
+ imx_drm_device_put();
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void __exit imx_fb_helper_exit(void)
+{
+ imx_drm_fbdev_fini(imx_fb_helper);
+ imx_drm_device_put();
+}
+
+late_initcall(imx_fb_helper_init);
+module_exit(imx_fb_helper_exit);
+
+MODULE_DESCRIPTION("Freescale i.MX legacy fb driver");
+MODULE_AUTHOR("Sascha Hauer, Pengutronix");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/imx-gem.c b/drivers/gpu/drm/imx/imx-gem.c
new file mode 100644
index 0000000..b0866fb
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-gem.c
@@ -0,0 +1,343 @@
+/*
+ * i.MX drm driver
+ *
+ * Copyright (C) 2012 Sascha Hauer, Pengutronix
+ *
+ * Based on Samsung Exynos code
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <drm/drmP.h>
+#include <drm/drm.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "imx-drm.h"
+
+static int lowlevel_buffer_allocate(struct drm_device *drm,
+ struct imx_drm_buf_entry *entry)
+{
+ entry->vaddr = dma_alloc_writecombine(drm->dev, entry->size,
+ (dma_addr_t *)&entry->paddr, GFP_KERNEL);
+ if (!entry->vaddr) {
+ dev_err(drm->dev, "failed to allocate buffer.\n");
+ return -ENOMEM;
+ }
+
+ dev_dbg(drm->dev, "allocated : vaddr(0x%x), paddr(0x%x), size(0x%x)\n",
+ (unsigned int)entry->vaddr, entry->paddr, entry->size);
+
+ return 0;
+}
+
+static void lowlevel_buffer_free(struct drm_device *drm,
+ struct imx_drm_buf_entry *entry)
+{
+ dma_free_writecombine(drm->dev, entry->size, entry->vaddr,
+ entry->paddr);
+}
+
+struct imx_drm_buf_entry *imx_drm_buf_create(struct drm_device *drm,
+ unsigned int size)
+{
+ struct imx_drm_buf_entry *entry;
+
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return ERR_PTR(-ENOMEM);
+
+ entry->size = size;
+
+ /*
+ * allocate memory region with size and set the memory information
+ * to vaddr and paddr of a entry object.
+ */
+ if (lowlevel_buffer_allocate(drm, entry) < 0) {
+ kfree(entry);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ return entry;
+}
+
+void imx_drm_buf_destroy(struct drm_device *drm,
+ struct imx_drm_buf_entry *entry)
+{
+ lowlevel_buffer_free(drm, entry);
+
+ kfree(entry);
+ entry = NULL;
+}
+EXPORT_SYMBOL_GPL(imx_drm_buf_destroy);
+
+static unsigned int convert_to_vm_err_msg(int msg)
+{
+ unsigned int out_msg;
+
+ switch (msg) {
+ case 0:
+ case -ERESTARTSYS:
+ case -EINTR:
+ out_msg = VM_FAULT_NOPAGE;
+ break;
+
+ case -ENOMEM:
+ out_msg = VM_FAULT_OOM;
+ break;
+
+ default:
+ out_msg = VM_FAULT_SIGBUS;
+ break;
+ }
+
+ return out_msg;
+}
+
+static unsigned int get_gem_mmap_offset(struct drm_gem_object *obj)
+{
+ return (unsigned int)obj->map_list.hash.key << PAGE_SHIFT;
+}
+
+static struct imx_drm_gem_obj *imx_drm_gem_create(struct drm_device *drm,
+ unsigned int size)
+{
+ struct imx_drm_gem_obj *imx_drm_gem_obj;
+ struct imx_drm_buf_entry *entry;
+ struct drm_gem_object *obj;
+ int ret;
+
+ size = roundup(size, PAGE_SIZE);
+
+ imx_drm_gem_obj = kzalloc(sizeof(*imx_drm_gem_obj), GFP_KERNEL);
+ if (!imx_drm_gem_obj)
+ return ERR_PTR(-ENOMEM);
+
+ /* allocate the new buffer object and memory region. */
+ entry = imx_drm_buf_create(drm, size);
+ if (!entry) {
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ imx_drm_gem_obj->entry = entry;
+
+ obj = &imx_drm_gem_obj->base;
+
+ ret = drm_gem_object_init(drm, obj, size);
+ if (ret) {
+ dev_err(drm->dev, "initializing GEM object failed with %d\n", ret);
+ goto err_obj_init;
+ }
+
+ ret = drm_gem_create_mmap_offset(obj);
+ if (ret) {
+ dev_err(drm->dev, "creating mmap offset failed with %d\n", ret);
+ goto err_create_mmap_offset;
+ }
+
+ return imx_drm_gem_obj;
+
+err_create_mmap_offset:
+ drm_gem_object_release(obj);
+
+err_obj_init:
+ imx_drm_buf_destroy(drm, imx_drm_gem_obj->entry);
+
+err_alloc:
+ kfree(imx_drm_gem_obj);
+
+ return ERR_PTR(ret);
+}
+
+static struct imx_drm_gem_obj *imx_drm_gem_create_with_handle(struct drm_file *file_priv,
+ struct drm_device *drm, unsigned int size,
+ unsigned int *handle)
+{
+ struct imx_drm_gem_obj *imx_drm_gem_obj;
+ struct drm_gem_object *obj;
+ int ret;
+
+ imx_drm_gem_obj = imx_drm_gem_create(drm, size);
+ if (IS_ERR(imx_drm_gem_obj))
+ return imx_drm_gem_obj;
+
+ obj = &imx_drm_gem_obj->base;
+
+ /*
+ * 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;
+
+ /* drop reference from allocate - handle holds it now. */
+ drm_gem_object_unreference_unlocked(obj);
+
+ return imx_drm_gem_obj;
+
+err_handle_create:
+ imx_drm_gem_free_object(obj);
+
+ return ERR_PTR(ret);
+}
+
+static int imx_drm_gem_mmap_buffer(struct file *filp,
+ struct vm_area_struct *vma)
+{
+ struct drm_gem_object *obj = filp->private_data;
+ struct imx_drm_gem_obj *imx_drm_gem_obj = to_imx_drm_gem_obj(obj);
+ struct imx_drm_buf_entry *entry;
+ unsigned long pfn, vm_size;
+
+ vma->vm_flags |= VM_IO | VM_RESERVED;
+
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ vma->vm_file = filp;
+
+ vm_size = vma->vm_end - vma->vm_start;
+ /*
+ * an entry contains information to physically continuous memory
+ * allocated by user request or at framebuffer creation.
+ */
+ entry = imx_drm_gem_obj->entry;
+
+ /* check if user-requested size is valid. */
+ if (vm_size > entry->size)
+ return -EINVAL;
+
+ /*
+ * get page frame number to physical memory to be mapped
+ * to user space.
+ */
+ pfn = imx_drm_gem_obj->entry->paddr >> PAGE_SHIFT;
+
+ if (remap_pfn_range(vma, vma->vm_start, pfn, vm_size,
+ vma->vm_page_prot)) {
+ dev_err(obj->dev->dev, "failed to remap pfn range.\n");
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static const struct file_operations imx_drm_gem_fops = {
+ .mmap = imx_drm_gem_mmap_buffer,
+};
+
+int imx_drm_gem_init_object(struct drm_gem_object *obj)
+{
+ return 0;
+}
+
+void imx_drm_gem_free_object(struct drm_gem_object *gem_obj)
+{
+ struct imx_drm_gem_obj *imx_drm_gem_obj;
+
+ if (gem_obj->map_list.map)
+ drm_gem_free_mmap_offset(gem_obj);
+
+ drm_gem_object_release(gem_obj);
+
+ imx_drm_gem_obj = to_imx_drm_gem_obj(gem_obj);
+
+ imx_drm_buf_destroy(gem_obj->dev, imx_drm_gem_obj->entry);
+
+ kfree(imx_drm_gem_obj);
+}
+
+int imx_drm_gem_dumb_create(struct drm_file *file_priv,
+ struct drm_device *dev, struct drm_mode_create_dumb *args)
+{
+ struct imx_drm_gem_obj *imx_drm_gem_obj;
+
+ /* FIXME: This should be configured by the crtc driver */
+ args->pitch = args->width * args->bpp >> 3;
+ args->size = args->pitch * args->height;
+
+ imx_drm_gem_obj = imx_drm_gem_create_with_handle(file_priv, dev, args->size,
+ &args->handle);
+ if (IS_ERR(imx_drm_gem_obj))
+ return PTR_ERR(imx_drm_gem_obj);
+
+ return 0;
+}
+
+int imx_drm_gem_dumb_map_offset(struct drm_file *file_priv,
+ struct drm_device *drm, uint32_t handle, uint64_t *offset)
+{
+ struct imx_drm_gem_obj *imx_drm_gem_obj;
+ struct drm_gem_object *obj;
+
+ mutex_lock(&drm->struct_mutex);
+
+ obj = drm_gem_object_lookup(drm, file_priv, handle);
+ if (!obj) {
+ dev_err(drm->dev, "failed to lookup gem object\n");
+ mutex_unlock(&drm->struct_mutex);
+ return -EINVAL;
+ }
+
+ imx_drm_gem_obj = to_imx_drm_gem_obj(obj);
+
+ *offset = get_gem_mmap_offset(&imx_drm_gem_obj->base);
+
+ drm_gem_object_unreference(obj);
+
+ mutex_unlock(&drm->struct_mutex);
+
+ return 0;
+}
+
+int imx_drm_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
+{
+ struct drm_gem_object *obj = vma->vm_private_data;
+ struct imx_drm_gem_obj *imx_drm_gem_obj = to_imx_drm_gem_obj(obj);
+ struct drm_device *dev = obj->dev;
+ unsigned long pfn;
+ pgoff_t page_offset;
+ int ret;
+
+ page_offset = ((unsigned long)vmf->virtual_address -
+ vma->vm_start) >> PAGE_SHIFT;
+
+ mutex_lock(&dev->struct_mutex);
+
+ pfn = (imx_drm_gem_obj->entry->paddr >> PAGE_SHIFT) + page_offset;
+
+ ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn);
+
+ mutex_unlock(&dev->struct_mutex);
+
+ return convert_to_vm_err_msg(ret);
+}
+
+int imx_drm_gem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int ret;
+
+ ret = drm_gem_mmap(filp, vma);
+ if (ret)
+ return ret;
+
+ vma->vm_flags &= ~VM_PFNMAP;
+ vma->vm_flags |= VM_MIXEDMAP;
+
+ return ret;
+}
+
+
+int imx_drm_gem_dumb_destroy(struct drm_file *file_priv,
+ struct drm_device *dev, unsigned int handle)
+{
+ return drm_gem_handle_delete(file_priv, handle);
+}
diff --git a/drivers/gpu/drm/imx/imx-lcdc-crtc.c b/drivers/gpu/drm/imx/imx-lcdc-crtc.c
new file mode 100644
index 0000000..e77c015
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-lcdc-crtc.c
@@ -0,0 +1,517 @@
+/*
+ * i.MX LCDC crtc driver
+ *
+ * Copyright (C) 2012 Sascha Hauer, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <linux/fb.h>
+#include <linux/clk.h>
+#include <asm/fb.h>
+#include <linux/module.h>
+#include <mach/hardware.h>
+#include <mach/imxfb.h>
+#include <generated/mach-types.h>
+
+#include "imx-drm.h"
+
+#define LCDC_SSA 0x00
+#define LCDC_SIZE 0x04
+#define LCDC_VPW 0x08
+#define LCDC_CPOS 0x0C
+#define LCDC_LCWHB 0x10
+#define LCDC_LCHCC 0x14
+#define LCDC_PCR 0x18
+#define LCDC_HCR 0x1C
+#define LCDC_VCR 0x20
+#define LCDC_POS 0x24
+#define LCDC_LSCR1 0x28
+#define LCDC_PWMR 0x2C
+#define LCDC_DMACR 0x30
+#define LCDC_RMCR 0x34
+#define LCDC_LCDICR 0x38
+#define LCDC_LIER 0x3c
+#define LCDC_LISR 0x40
+
+#define SIZE_XMAX(x) ((((x) >> 4) & 0x3f) << 20)
+
+#define YMAX_MASK (cpu_is_mx1() ? 0x1ff : 0x3ff)
+#define SIZE_YMAX(y) ((y) & YMAX_MASK)
+
+#define VPW_VPW(x) ((x) & 0x3ff)
+
+#define HCR_H_WIDTH(x) (((x) & 0x3f) << 26)
+#define HCR_H_WAIT_1(x) (((x) & 0xff) << 8)
+#define HCR_H_WAIT_2(x) ((x) & 0xff)
+
+#define VCR_V_WIDTH(x) (((x) & 0x3f) << 26)
+#define VCR_V_WAIT_1(x) (((x) & 0xff) << 8)
+#define VCR_V_WAIT_2(x) ((x) & 0xff)
+
+#define RMCR_LCDC_EN_MX1 (1 << 1)
+
+#define RMCR_SELF_REF (1 << 0)
+
+#define LIER_EOF (1 << 1)
+
+struct imx_crtc {
+ struct drm_crtc base;
+ struct imx_drm_crtc *imx_drm_crtc;
+ int di_no;
+ int enabled;
+ void __iomem *regs;
+ u32 pwmr;
+ u32 lscr1;
+ u32 dmacr;
+ u32 pcr;
+ struct clk *clk;
+ struct device *dev;
+ int vblank_enable;
+
+ struct drm_pending_vblank_event *page_flip_event;
+ struct drm_framebuffer *newfb;
+};
+
+#define to_imx_crtc(x) container_of(x, struct imx_crtc, base)
+
+static void imx_crtc_load_lut(struct drm_crtc *crtc)
+{
+}
+
+#define PCR_BPIX_8 (3 << 25)
+#define PCR_BPIX_12 (4 << 25)
+#define PCR_BPIX_16 (5 << 25)
+#define PCR_BPIX_18 (6 << 25)
+#define PCR_END_SEL (1 << 18)
+#define PCR_END_BYTE_SWAP (1 << 17)
+
+const char *fourcc_to_str(u32 fourcc)
+{
+ static char buf[5];
+
+ *(u32 *)buf = fourcc;
+ buf[4] = 0;
+
+ return buf;
+}
+
+static int imx_drm_crtc_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+ struct drm_framebuffer *fb = crtc->fb;
+ int lower_margin = mode->vsync_start - mode->vdisplay;
+ int upper_margin = mode->vtotal - mode->vsync_end;
+ int vsync_len = mode->vsync_end - mode->vsync_start;
+ int hsync_len = mode->hsync_end - mode->hsync_start;
+ int right_margin = mode->hsync_start - mode->hdisplay;
+ int left_margin = mode->htotal - mode->hsync_end;
+ unsigned long lcd_clk;
+ u32 pcr;
+
+ lcd_clk = clk_get_rate(imx_crtc->clk) / 1000;
+
+ if (!mode->clock)
+ return -EINVAL;
+
+ pcr = DIV_ROUND_CLOSEST(lcd_clk, mode->clock);
+ if (--pcr > 0x3f)
+ pcr = 0x3f;
+
+ switch (fb->pixel_format) {
+ case DRM_FORMAT_XRGB8888:
+ case DRM_FORMAT_ARGB8888:
+ pcr |= PCR_BPIX_18;
+ pcr |= PCR_END_SEL | PCR_END_BYTE_SWAP;
+ break;
+ case DRM_FORMAT_RGB565:
+ if (cpu_is_mx1())
+ pcr |= PCR_BPIX_12;
+ else
+ pcr |= PCR_BPIX_16;
+ break;
+ case DRM_FORMAT_RGB332:
+ pcr |= PCR_BPIX_8;
+ break;
+ default:
+ dev_err(imx_crtc->dev, "unsupported pixel format %s\n",
+ fourcc_to_str(fb->pixel_format));
+ return -EINVAL;
+ }
+
+ /* add sync polarities */
+ pcr |= imx_crtc->pcr & ~(0x3f | (7 << 25));
+
+ dev_dbg(imx_crtc->dev,
+ "xres=%d hsync_len=%d left_margin=%d right_margin=%d\n",
+ mode->hdisplay, hsync_len,
+ left_margin, right_margin);
+ dev_dbg(imx_crtc->dev,
+ "yres=%d vsync_len=%d upper_margin=%d lower_margin=%d\n",
+ mode->vdisplay, vsync_len,
+ upper_margin, lower_margin);
+
+ writel(VPW_VPW(mode->hdisplay * fb->bits_per_pixel / 8 / 4),
+ imx_crtc->regs + LCDC_VPW);
+
+ writel(HCR_H_WIDTH(hsync_len - 1) |
+ HCR_H_WAIT_1(right_margin - 1) |
+ HCR_H_WAIT_2(left_margin - 3),
+ imx_crtc->regs + LCDC_HCR);
+
+ writel(VCR_V_WIDTH(vsync_len) |
+ VCR_V_WAIT_1(lower_margin) |
+ VCR_V_WAIT_2(upper_margin),
+ imx_crtc->regs + LCDC_VCR);
+
+ writel(SIZE_XMAX(mode->hdisplay) | SIZE_YMAX(mode->vdisplay),
+ imx_crtc->regs + LCDC_SIZE);
+
+ writel(pcr, imx_crtc->regs + LCDC_PCR);
+ writel(imx_crtc->pwmr, imx_crtc->regs + LCDC_PWMR);
+ writel(imx_crtc->lscr1, imx_crtc->regs + LCDC_LSCR1);
+ /* reset default */
+ writel(0x00040060, imx_crtc->regs + LCDC_DMACR);
+
+ return 0;
+}
+
+static int imx_drm_set_base(struct drm_crtc *crtc, int x, int y)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+ struct imx_drm_buf_entry *entry;
+ struct drm_framebuffer *fb = crtc->fb;
+ unsigned long phys;
+
+ entry = imx_drm_fb_get_buf(fb);
+ if (!entry)
+ return -EFAULT;
+
+ phys = entry->paddr;
+ phys += x * (fb->bits_per_pixel >> 3);
+ phys += y * fb->pitches[0];
+
+ dev_dbg(imx_crtc->dev, "%s: phys: 0x%lx\n", __func__, phys);
+ dev_dbg(imx_crtc->dev, "%s: xy: %dx%d\n", __func__, x, y);
+
+ writel(phys, imx_crtc->regs + LCDC_SSA);
+
+ return 0;
+}
+
+static int imx_crtc_mode_set(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode,
+ int x, int y,
+ struct drm_framebuffer *old_fb)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+
+ imx_drm_set_base(crtc, x, y);
+
+ dev_dbg(imx_crtc->dev, "mode->hdisplay: %d\n", mode->hdisplay);
+ dev_dbg(imx_crtc->dev, "mode->vdisplay: %d\n", mode->vdisplay);
+
+ return imx_drm_crtc_set(crtc, mode);
+}
+
+static void imx_crtc_enable(struct imx_crtc *imx_crtc)
+{
+ if (!imx_crtc->enabled)
+ clk_enable(imx_crtc->clk);
+ imx_crtc->enabled = 1;
+}
+
+static void imx_crtc_disable(struct imx_crtc *imx_crtc)
+{
+ if (imx_crtc->enabled)
+ clk_disable(imx_crtc->clk);
+ imx_crtc->enabled = 0;
+}
+
+static void imx_crtc_dpms(struct drm_crtc *crtc, int mode)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+
+ dev_dbg(imx_crtc->dev, "%s mode: %d\n", __func__, mode);
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ imx_crtc_enable(imx_crtc);
+ break;
+ default:
+ imx_crtc_disable(imx_crtc);
+ break;
+ }
+}
+
+static bool imx_crtc_mode_fixup(struct drm_crtc *crtc,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void imx_crtc_prepare(struct drm_crtc *crtc)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+
+ imx_crtc_disable(imx_crtc);
+}
+
+static void imx_crtc_commit(struct drm_crtc *crtc)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+
+ imx_crtc_enable(imx_crtc);
+}
+
+static struct drm_crtc_helper_funcs imx_helper_funcs = {
+ .dpms = imx_crtc_dpms,
+ .mode_fixup = imx_crtc_mode_fixup,
+ .mode_set = imx_crtc_mode_set,
+ .prepare = imx_crtc_prepare,
+ .commit = imx_crtc_commit,
+ .load_lut = imx_crtc_load_lut,
+};
+
+static void imx_drm_handle_pageflip(struct imx_crtc *imx_crtc)
+{
+ struct drm_pending_vblank_event *e;
+ struct timeval now;
+ unsigned long flags;
+ struct drm_device *drm = imx_crtc->base.dev;
+
+ spin_lock_irqsave(&drm->event_lock, flags);
+
+ e = imx_crtc->page_flip_event;
+
+ if (!e) {
+ spin_unlock_irqrestore(&drm->event_lock, flags);
+ return;
+ }
+
+ do_gettimeofday(&now);
+ e->event.sequence = 0;
+ e->event.tv_sec = now.tv_sec;
+ e->event.tv_usec = now.tv_usec;
+ imx_crtc->page_flip_event = NULL;
+
+ list_add_tail(&e->base.link, &e->base.file_priv->event_list);
+
+ wake_up_interruptible(&e->base.file_priv->event_wait);
+
+ spin_unlock_irqrestore(&drm->event_lock, flags);
+}
+
+static int imx_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+
+ writel(LIER_EOF, imx_crtc->regs + LCDC_LIER);
+
+ return 0;
+}
+
+static void imx_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+
+ writel(0, imx_crtc->regs + LCDC_LIER);
+}
+
+static irqreturn_t imx_irq_handler(int irq, void *dev_id)
+{
+ struct imx_crtc *imx_crtc = dev_id;
+ struct drm_device *drm = imx_crtc->base.dev;
+
+ /* Acknowledge interrupt */
+ readl(imx_crtc->regs + LCDC_LISR);
+
+ drm_handle_vblank(drm, 0);
+
+ if (imx_crtc->newfb) {
+ imx_crtc->base.fb = imx_crtc->newfb;
+ imx_crtc->newfb = NULL;
+ imx_drm_set_base(&imx_crtc->base, 0, 0);
+ imx_drm_handle_pageflip(imx_crtc);
+ imx_drm_crtc_vblank_put(imx_crtc->imx_drm_crtc);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int imx_page_flip(struct drm_crtc *crtc,
+ struct drm_framebuffer *fb,
+ struct drm_pending_vblank_event *event)
+{
+ struct imx_crtc *imx_crtc = to_imx_crtc(crtc);
+
+ if (imx_crtc->newfb)
+ return -EBUSY;
+
+ imx_crtc->newfb = fb;
+ imx_crtc->page_flip_event = event;
+ imx_drm_crtc_vblank_get(imx_crtc->imx_drm_crtc);
+
+ return 0;
+}
+
+static const struct drm_crtc_funcs imx_crtc_funcs = {
+ .page_flip = imx_page_flip,
+};
+
+static const struct imx_drm_crtc_helper_funcs imx_imx_drm_helper = {
+ .enable_vblank = imx_crtc_enable_vblank,
+ .disable_vblank = imx_crtc_disable_vblank,
+};
+
+#define DRIVER_NAME "imx-lcdc-crtc"
+
+/*
+ * the pcr bits to be allowed to set in platform data
+ */
+#define PDATA_PCR (PCR_PIXPOL | PCR_FLMPOL | PCR_LPPOL | \
+ PCR_CLKPOL | PCR_OEPOL | PCR_TFT | PCR_COLOR | \
+ PCR_PBSIZ_8 | PCR_ACD(0x7f) | PCR_ACD_SEL | \
+ PCR_SCLK_SEL | PCR_SHARP)
+
+static int __devinit imx_crtc_probe(struct platform_device *pdev)
+{
+ struct imx_crtc *imx_crtc;
+ struct resource *res;
+ int ret, irq;
+ u32 pcr_value = 0xf00080c0;
+ u32 lscr1_value = 0x00120300;
+ u32 pwmr_value = 0x00a903ff;
+
+ imx_crtc = devm_kzalloc(&pdev->dev, sizeof(*imx_crtc), GFP_KERNEL);
+ if (!imx_crtc)
+ return -ENOMEM;
+
+ imx_crtc->dev = &pdev->dev;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ res = devm_request_mem_region(&pdev->dev, res->start,
+ resource_size(res), DRIVER_NAME);
+ if (!res)
+ return -EBUSY;
+
+ imx_crtc->regs = devm_ioremap(&pdev->dev, res->start,
+ resource_size(res));
+ if (!imx_crtc->regs) {
+ dev_err(&pdev->dev, "Cannot map frame buffer registers\n");
+ return -EBUSY;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ ret = devm_request_irq(&pdev->dev, irq, imx_irq_handler, 0, "imx_drm",
+ imx_crtc);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "irq request failed with %d\n", ret);
+ return ret;
+ }
+
+ imx_crtc->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR(imx_crtc->clk)) {
+ ret = PTR_ERR(imx_crtc->clk);
+ dev_err(&pdev->dev, "unable to get clock: %d\n", ret);
+ return ret;
+ }
+
+ clk_prepare_enable(imx_crtc->clk);
+ imx_crtc->enabled = 1;
+
+ platform_set_drvdata(pdev, imx_crtc);
+
+ imx_crtc->pcr = pcr_value & PDATA_PCR;
+
+ if (imx_crtc->pcr != pcr_value)
+ dev_err(&pdev->dev, "invalid bits set in pcr: 0x%08x\n",
+ pcr_value & ~PDATA_PCR);
+
+ imx_crtc->lscr1 = lscr1_value;
+ imx_crtc->pwmr = pwmr_value;
+
+ ret = imx_drm_add_crtc(&imx_crtc->base,
+ &imx_crtc->imx_drm_crtc,
+ &imx_crtc_funcs, &imx_helper_funcs,
+ &imx_imx_drm_helper, THIS_MODULE);
+ if (ret)
+ goto err_init;
+
+ dev_info(&pdev->dev, "probed\n");
+
+ return 0;
+
+err_init:
+ clk_disable_unprepare(imx_crtc->clk);
+ clk_put(imx_crtc->clk);
+
+ return ret;
+}
+
+static int __devexit imx_crtc_remove(struct platform_device *pdev)
+{
+ struct imx_crtc *imx_crtc = platform_get_drvdata(pdev);
+
+ imx_drm_remove_crtc(imx_crtc->imx_drm_crtc);
+
+ writel(0, imx_crtc->regs + LCDC_LIER);
+
+ clk_disable_unprepare(imx_crtc->clk);
+ clk_put(imx_crtc->clk);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static const struct of_device_id imx_lcdc_dt_ids[] = {
+ { .compatible = "fsl,imx1-lcdc", .data = NULL, },
+ { .compatible = "fsl,imx21-lcdc", .data = NULL, },
+ { /* sentinel */ }
+};
+
+static struct platform_driver imx_crtc_driver = {
+ .remove = __devexit_p(imx_crtc_remove),
+ .probe = imx_crtc_probe,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .of_match_table = imx_lcdc_dt_ids,
+ },
+};
+
+static int __init imx_lcdc_init(void)
+{
+ return platform_driver_register(&imx_crtc_driver);
+}
+
+static void __exit imx_lcdc_exit(void)
+{
+ platform_driver_unregister(&imx_crtc_driver);
+}
+
+module_init(imx_lcdc_init);
+module_exit(imx_lcdc_exit)
+
+MODULE_DESCRIPTION("Freescale i.MX framebuffer driver");
+MODULE_AUTHOR("Sascha Hauer, Pengutronix");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/imx/imx-parallel-display.c b/drivers/gpu/drm/imx/imx-parallel-display.c
new file mode 100644
index 0000000..8c96113
--- /dev/null
+++ b/drivers/gpu/drm/imx/imx-parallel-display.c
@@ -0,0 +1,228 @@
+/*
+ * i.MX drm driver - parallel display implementation
+ *
+ * Copyright (C) 2012 Sascha Hauer, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <drm/drmP.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_crtc_helper.h>
+
+#include "imx-drm.h"
+
+#define con_to_imxpd(x) container_of(x, struct imx_parallel_display, connector)
+#define enc_to_imxpd(x) container_of(x, struct imx_parallel_display, encoder)
+
+struct imx_parallel_display {
+ struct drm_connector connector;
+ struct imx_drm_connector *imx_drm_connector;
+ struct drm_encoder encoder;
+ struct imx_drm_encoder *imx_drm_encoder;
+ struct device *dev;
+ void *edid;
+ int edid_len;
+};
+
+static enum drm_connector_status imx_pd_connector_detect(struct drm_connector *connector,
+ bool force)
+{
+ return connector_status_connected;
+}
+
+static void imx_pd_connector_destroy(struct drm_connector *connector)
+{
+ /* do not free here */
+}
+
+static int imx_pd_connector_get_modes(struct drm_connector *connector)
+{
+ struct imx_parallel_display *imxpd = con_to_imxpd(connector);
+ int ret;
+
+ drm_mode_connector_update_edid_property(connector, imxpd->edid);
+ ret = drm_add_edid_modes(connector, imxpd->edid);
+ connector->display_info.raw_edid = NULL;
+
+ return ret;
+}
+
+static int imx_pd_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ return 0;
+}
+
+static struct drm_encoder *imx_pd_connector_best_encoder(struct drm_connector *connector)
+{
+ struct imx_parallel_display *imxpd = con_to_imxpd(connector);
+
+ return &imxpd->encoder;
+}
+
+static void imx_pd_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+}
+
+static bool imx_pd_encoder_mode_fixup(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ return true;
+}
+
+static void imx_pd_encoder_prepare(struct drm_encoder *encoder)
+{
+}
+
+static void imx_pd_encoder_commit(struct drm_encoder *encoder)
+{
+}
+
+static void imx_pd_encoder_mode_set(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+}
+
+static void imx_pd_encoder_disable(struct drm_encoder *encoder)
+{
+}
+
+static void imx_pd_encoder_destroy(struct drm_encoder *encoder)
+{
+ /* do not free here */
+}
+
+struct drm_connector_funcs imx_pd_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = imx_pd_connector_detect,
+ .destroy = imx_pd_connector_destroy,
+};
+
+struct drm_connector_helper_funcs imx_pd_connector_helper_funcs = {
+ .get_modes = imx_pd_connector_get_modes,
+ .best_encoder = imx_pd_connector_best_encoder,
+ .mode_valid = imx_pd_connector_mode_valid,
+};
+
+static struct drm_encoder_funcs imx_pd_encoder_funcs = {
+ .destroy = imx_pd_encoder_destroy,
+};
+
+static struct drm_encoder_helper_funcs imx_pd_encoder_helper_funcs = {
+ .dpms = imx_pd_encoder_dpms,
+ .mode_fixup = imx_pd_encoder_mode_fixup,
+ .prepare = imx_pd_encoder_prepare,
+ .commit = imx_pd_encoder_commit,
+ .mode_set = imx_pd_encoder_mode_set,
+ .disable = imx_pd_encoder_disable,
+};
+
+static int imx_pd_register(struct imx_parallel_display *imxpd)
+{
+ int ret;
+
+ drm_mode_connector_attach_encoder(&imxpd->connector, &imxpd->encoder);
+
+ imxpd->connector.funcs = &imx_pd_connector_funcs;
+ imxpd->encoder.funcs = &imx_pd_encoder_funcs;
+
+ drm_encoder_helper_add(&imxpd->encoder, &imx_pd_encoder_helper_funcs);
+ ret = imx_drm_add_encoder(&imxpd->encoder, &imxpd->imx_drm_encoder, THIS_MODULE);
+ if (ret) {
+ dev_err(imxpd->dev, "adding encoder failed with %d\n", ret);
+ return ret;
+ }
+
+ drm_connector_helper_add(&imxpd->connector, &imx_pd_connector_helper_funcs);
+
+ ret = imx_drm_add_connector(&imxpd->connector, &imxpd->imx_drm_connector,
+ THIS_MODULE);
+ if (ret) {
+ imx_drm_remove_encoder(imxpd->imx_drm_encoder);
+ dev_err(imxpd->dev, "adding connector failed with %d\n", ret);
+ return ret;
+ }
+
+ imxpd->connector.encoder = &imxpd->encoder;
+
+ return 0;
+}
+
+static int __devinit imx_pd_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const u8 *edidp;
+ struct imx_parallel_display *imxpd;
+ int ret;
+
+ imxpd = devm_kzalloc(&pdev->dev, sizeof(*imxpd), GFP_KERNEL);
+ if (!imxpd)
+ return -ENOMEM;
+
+ edidp = of_get_property(np, "edid", &imxpd->edid_len);
+
+ imxpd->edid = kmemdup(edidp, imxpd->edid_len, GFP_KERNEL);
+ imxpd->encoder.possible_crtcs = 0x1;
+ imxpd->encoder.possible_clones = 0x1;
+ imxpd->dev = &pdev->dev;
+
+ ret = imx_pd_register(imxpd);
+ if (ret)
+ return ret;
+
+ platform_set_drvdata(pdev, imxpd);
+
+ return 0;
+}
+
+static int __devexit imx_pd_remove(struct platform_device *pdev)
+{
+ struct imx_parallel_display *imxpd = platform_get_drvdata(pdev);
+ struct drm_connector *connector = &imxpd->connector;
+ struct drm_encoder *encoder = &imxpd->encoder;
+
+ drm_mode_connector_detach_encoder(connector, encoder);
+
+ imx_drm_remove_connector(imxpd->imx_drm_connector);
+ imx_drm_remove_encoder(imxpd->imx_drm_encoder);
+
+ return 0;
+}
+
+static const struct of_device_id imx_pd_dt_ids[] = {
+ { .compatible = "fsl,imx-parallel-display", .data = NULL, },
+ { /* sentinel */ }
+};
+
+static struct platform_driver imx_pd_driver = {
+ .probe = imx_pd_probe,
+ .remove = __devexit_p(imx_pd_remove),
+ .driver = {
+ .of_match_table = imx_pd_dt_ids,
+ .name = "imx-parallel-display",
+ .owner = THIS_MODULE,
+ },
+};
+
+module_platform_driver(imx_pd_driver);
+
+MODULE_DESCRIPTION("i.MX parallel display driver");
+MODULE_AUTHOR("Sascha Hauer, Pengutronix");
+MODULE_LICENSE("GPL");
--
1.7.10
More information about the linux-arm-kernel
mailing list