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