diff options
author | Rob Clark <rob@ti.com> | 2011-09-01 08:05:21 +0100 |
---|---|---|
committer | Andy Green <andy.green@linaro.org> | 2011-09-01 08:05:21 +0100 |
commit | 741b68d61248d1b83cd57b27d3da50b5e57aebd2 (patch) | |
tree | 1581f751f981b38a8f2d87146ea42f656f4dea05 | |
parent | f810df88fd6ec80166a3fb36d913505caa4212ea (diff) |
Add omap drm display driver
A DSS based DRM display driver, which provides a plugin interface for
3d/2d accelerators to register. The core driver handles construction
of CRTC/encoder/connectors to represent the hardware, and support KMS.
This driver replaces omapfb. The omap drm driver allocates framebuffer
memory and implements (with the help of drm_fb_helper) the legacy fbdev
interface.
The driver maps CRTCs to overlays, encoders to overlay-managers, and
connectors to dssdev's.
Signed-off-by: Rob Clark <rob@ti.com>
Signed-off-by: Ricardo Salveti de Araujo <ricardo.salveti@canonical.com>
-rw-r--r-- | drivers/gpu/drm/Kconfig | 1 | ||||
-rw-r--r-- | drivers/staging/Kconfig | 2 | ||||
-rw-r--r-- | drivers/staging/Makefile | 1 | ||||
-rw-r--r-- | drivers/staging/omapdrm/Kconfig | 25 | ||||
-rw-r--r-- | drivers/staging/omapdrm/Makefile | 8 | ||||
-rw-r--r-- | drivers/staging/omapdrm/TODO.txt | 10 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_connector.c | 398 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_crtc.c | 325 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_drv.c | 751 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_drv.h | 68 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_encoder.c | 198 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_fb.c | 251 | ||||
-rw-r--r-- | drivers/staging/omapdrm/omap_fbdev.c | 279 | ||||
-rw-r--r-- | drivers/video/omap2/omapfb/Kconfig | 4 | ||||
-rw-r--r-- | include/linux/Kbuild | 1 | ||||
-rw-r--r-- | include/linux/omap_drm.h | 88 |
16 files changed, 2408 insertions, 2 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index b493663c7ba..89972cc49eb 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -158,3 +158,4 @@ config DRM_SAVAGE help Choose this option if you have a Savage3D/4/SuperSavage/Pro/Twister chipset. If M is selected the module will be called savage. + diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 196284dc2f3..99b754710b3 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -160,4 +160,6 @@ source "drivers/staging/mei/Kconfig" source "drivers/staging/nvec/Kconfig" +source "drivers/staging/omapdrm/Kconfig" + endif # STAGING diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index fa41b9c2378..91ec21f9472 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -71,3 +71,4 @@ obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4) += ste_rmi4/ obj-$(CONFIG_DRM_PSB) += gma500/ obj-$(CONFIG_INTEL_MEI) += mei/ obj-$(CONFIG_MFD_NVEC) += nvec/ +obj-$(CONFIG_DRM_OMAP) += omapdrm/ diff --git a/drivers/staging/omapdrm/Kconfig b/drivers/staging/omapdrm/Kconfig new file mode 100644 index 00000000000..513ff916075 --- /dev/null +++ b/drivers/staging/omapdrm/Kconfig @@ -0,0 +1,25 @@ + +config DRM_OMAP + tristate "OMAP DRM (EXPERIMENTAL)" + depends on DRM && !CONFIG_FB_OMAP2 + select DRM_KMS_HELPER + select OMAP2_VRAM + select OMAP2_DSS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + default y + help + DRM display driver for OMAP2/3/4 based boards. + +config DRM_OMAP_NUM_CRTCS + int "Number of CRTCs" + range 1 10 + default 1 if ARCH_OMAP2 || ARCH_OMAP3 + default 2 if ARCH_OMAP4 + depends on DRM_OMAP + help + Select the number of video overlays which can be used as framebuffers. + The remaining overlays are reserved for video. + diff --git a/drivers/staging/omapdrm/Makefile b/drivers/staging/omapdrm/Makefile new file mode 100644 index 00000000000..0fd44de2ce3 --- /dev/null +++ b/drivers/staging/omapdrm/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. + +ccflags-y := -Iinclude/drm -Werror +omapdrm-y := omap_drv.o omap_crtc.o omap_encoder.o omap_connector.o omap_fb.o omap_fbdev.o + +obj-$(CONFIG_DRM_OMAP) += omapdrm.o diff --git a/drivers/staging/omapdrm/TODO.txt b/drivers/staging/omapdrm/TODO.txt new file mode 100644 index 00000000000..65a2c8239a2 --- /dev/null +++ b/drivers/staging/omapdrm/TODO.txt @@ -0,0 +1,10 @@ ++ check error handling/cleanup paths ++ GEM buffer support.. don't ignore bo id in omap_fb + ++ plugins should register consecutive ioctl's relative to a base ioctl + # that is assigned by omap_drm ++ omap_drm should have an ioctl to query by plugin name the assigned + base ioctl # ++ userspace should use this base ioctl # to calculate the actual ioctl + # relative to that base. + diff --git a/drivers/staging/omapdrm/omap_connector.c b/drivers/staging/omapdrm/omap_connector.c new file mode 100644 index 00000000000..f0c30213609 --- /dev/null +++ b/drivers/staging/omapdrm/omap_connector.c @@ -0,0 +1,398 @@ +/* + * linux/drivers/staging/omapdrm/omap_connector.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/omap_drm.h> +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +/* + * connector funcs + */ + +#define to_omap_connector(x) container_of(x, struct omap_connector, base) + +struct omap_connector { + struct drm_connector base; + struct omap_dss_device *dssdev; +}; + +static inline void copy_timings_omap_to_drm(struct drm_display_mode *mode, + struct omap_video_timings *timings) +{ + mode->clock = timings->pixel_clock; + + mode->hdisplay = timings->x_res; + mode->hsync_start = mode->hdisplay + timings->hfp; + mode->hsync_end = mode->hsync_start + timings->hsw; + mode->htotal = mode->hsync_end + timings->hbp; + + mode->vdisplay = timings->y_res; + mode->vsync_start = mode->vdisplay + timings->vfp; + mode->vsync_end = mode->vsync_start + timings->vsw; + mode->vtotal = mode->vsync_end + timings->vbp; + + /* note: whether or not it is interlaced, +/- h/vsync, etc, + * which should be set in the mode flags, is not exposed in + * the omap_video_timings struct.. but hdmi driver tracks + * those separately so all we have to have to set the mode + * is the way to recover these timings values, and the + * omap_dss_driver would do the rest. + */ +} + +static inline void copy_timings_drm_to_omap(struct omap_video_timings *timings, + struct drm_display_mode *mode) +{ + timings->pixel_clock = mode->clock; + + timings->x_res = mode->hdisplay; + timings->hfp = mode->hsync_start - mode->hdisplay; + timings->hsw = mode->hsync_end - mode->hsync_start; + timings->hbp = mode->htotal - mode->hsync_end; + + timings->y_res = mode->vdisplay; + timings->vfp = mode->vsync_start - mode->vdisplay; + timings->vsw = mode->vsync_end - mode->vsync_start; + timings->vbp = mode->vtotal - mode->vsync_end; +} + +void omap_connector_dpms(struct drm_connector *connector, int mode) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + + /* TODO: add API in DSS to suspend/resume individual displays.. */ + + DBG("%s: %d", dssdev->name, mode); +} + +enum drm_connector_status omap_connector_detect( + struct drm_connector *connector, bool force) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + enum drm_connector_status ret; + + if (dssdrv->is_detected(dssdev)) { + ret = connector_status_connected; + } else { + ret = connector_status_disconnected; + } + + VERB("%s: %d (force=%d)", omap_connector->dssdev->name, ret, force); + + return ret; +} + +static void omap_connector_destroy(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + + DBG("%s", omap_connector->dssdev->name); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(omap_connector); +} + +#define MAX_EDID 256 + +static int omap_connector_get_modes(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct drm_device *dev = connector->dev; + int n = 0; + + DBG("%s", omap_connector->dssdev->name); + + /* if display exposes EDID, then we parse that in the normal way to + * build table of supported modes.. otherwise (ie. fixed resolution + * LCD panels) we just return a single mode corresponding to the + * currently configured timings: + */ + if (dssdrv->get_edid) { + void *edid = kzalloc(MAX_EDID, GFP_KERNEL); + + if ((dssdrv->get_edid(dssdev, edid, MAX_EDID) == 0) && + drm_edid_is_valid(edid)) { + drm_mode_connector_update_edid_property(connector, edid); + n = drm_add_edid_modes(connector, edid); + kfree(connector->display_info.raw_edid); + connector->display_info.raw_edid = edid; + } else { + drm_mode_connector_update_edid_property(connector, NULL); + connector->display_info.raw_edid = NULL; + kfree(edid); + } + } else { + struct drm_display_mode *mode = drm_mode_create(dev); + struct omap_video_timings timings; + + dssdrv->get_timings(dssdev, &timings); + + copy_timings_omap_to_drm(mode, &timings); + + mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + + n = 1; + } + + return n; +} + +static int omap_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct omap_video_timings timings = {0}; + struct drm_device *dev = connector->dev; + struct drm_display_mode *new_mode; + int ret = MODE_BAD; + + copy_timings_drm_to_omap(&timings, mode); + mode->vrefresh = drm_mode_vrefresh(mode); + + if (!dssdrv->check_timings(dssdev, &timings)) { + /* check if vrefresh is still valid */ + new_mode = drm_mode_duplicate(dev, mode); + new_mode->clock = timings.pixel_clock; + new_mode->vrefresh = 0; + if (mode->vrefresh == drm_mode_vrefresh(new_mode)) + ret = MODE_OK; + drm_mode_destroy(dev, new_mode); + } + + DBG("connector: mode %s: " + "%d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + (ret == MODE_OK) ? "valid" : "invalid", + mode->base.id, mode->name, mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, mode->type, mode->flags); + + return ret; +} + +struct drm_encoder * omap_connector_attached_encoder( + struct drm_connector *connector) +{ + int i; + struct omap_connector *omap_connector = to_omap_connector(connector); + + for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { + struct drm_mode_object *obj; + + if (connector->encoder_ids[i] == 0) + break; + + obj = drm_mode_object_find(connector->dev, + connector->encoder_ids[i], + DRM_MODE_OBJECT_ENCODER); + + if (obj) { + struct drm_encoder *encoder = obj_to_encoder(obj); + struct omap_overlay_manager *mgr = + omap_encoder_get_manager(encoder); + DBG("%s: found %s", omap_connector->dssdev->name, + mgr->name); + return encoder; + } + } + + DBG("%s: no encoder", omap_connector->dssdev->name); + + return NULL; +} + +static const struct drm_connector_funcs omap_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = omap_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = omap_connector_destroy, +}; + +static const struct drm_connector_helper_funcs omap_connector_helper_funcs = { + .get_modes = omap_connector_get_modes, + .mode_valid = omap_connector_mode_valid, + .best_encoder = omap_connector_attached_encoder, +}; + +/* called from encoder when mode is set, to propagate settings to the dssdev */ +void omap_connector_mode_set(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct drm_device *dev = connector->dev; + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + struct omap_video_timings timings; + + copy_timings_drm_to_omap(&timings, mode); + + DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", + omap_connector->dssdev->name, + mode->base.id, mode->name, mode->vrefresh, mode->clock, + mode->hdisplay, mode->hsync_start, + mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, + mode->vsync_end, mode->vtotal, mode->type, mode->flags); + + if (dssdrv->check_timings(dssdev, &timings)) { + dev_err(dev->dev, "could not set timings\n"); + return; + } + + dssdrv->set_timings(dssdev, &timings); +} + +enum omap_dss_update_mode omap_connector_get_update_mode( + struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + + DBG("%s", omap_connector->dssdev->name); + + if (dssdrv->get_update_mode) { + return dssdrv->get_update_mode(dssdev); + } + + return -1; +} +EXPORT_SYMBOL(omap_connector_get_update_mode); + +int omap_connector_set_update_mode(struct drm_connector *connector, + enum omap_dss_update_mode mode) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + + DBG("%s: %d", omap_connector->dssdev->name, mode); + + if (dssdrv->set_update_mode) { + return dssdrv->set_update_mode(dssdev, mode); + } + + return -EINVAL; +} +EXPORT_SYMBOL(omap_connector_set_update_mode); + +int omap_connector_sync(struct drm_connector *connector) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + struct omap_dss_device *dssdev = omap_connector->dssdev; + struct omap_dss_driver *dssdrv = dssdev->driver; + + DBG("%s", omap_connector->dssdev->name); + + if (dssdrv->sync) { + return dssdrv->sync(dssdev); + } + + return -EINVAL; +} +EXPORT_SYMBOL(omap_connector_sync); + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +void omap_connector_flush(struct drm_connector *connector, + int x, int y, int w, int h) +{ + struct omap_connector *omap_connector = to_omap_connector(connector); + + /* TODO: enable when supported in dss */ + VERB("%s: %d,%d, %dx%d", omap_connector->dssdev->name, x, y, w, h); +} + +/* initialize connector */ +struct drm_connector * omap_connector_init(struct drm_device *dev, + int connector_type, struct omap_dss_device *dssdev) +{ + struct drm_connector *connector = NULL; + struct omap_connector *omap_connector; + + DBG("%s", dssdev->name); + + omap_connector = kzalloc(sizeof(struct omap_connector), GFP_KERNEL); + if (!omap_connector) { + dev_err(dev->dev, "could not allocate connector\n"); + goto fail; + } + + omap_connector->dssdev = dssdev; + connector = &omap_connector->base; + + drm_connector_init(dev, connector, &omap_connector_funcs, + connector_type); + drm_connector_helper_add(connector, &omap_connector_helper_funcs); + +#if 0 /* enable when dss2 supports hotplug */ + if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_HPD) + connector->polled = 0; + else +#endif + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 1; + connector->doublescan_allowed = 0; + + drm_sysfs_connector_add(connector); + + /* store resume info for suspended displays */ + switch (dssdev->state) { + case OMAP_DSS_DISPLAY_SUSPENDED: + dssdev->activate_after_resume = true; + break; + case OMAP_DSS_DISPLAY_DISABLED: + if (dssdev->driver) { + int ret = dssdev->driver->enable(dssdev); + if (ret) { + DBG("%s: failed to enable: %d", + dssdev->name, ret); + dssdev->driver->disable(dssdev); + goto fail; + } + } + break; + default: + break; + } + + return connector; + +fail: + if (connector) { + omap_connector_destroy(connector); + } + + return NULL; +} diff --git a/drivers/staging/omapdrm/omap_crtc.c b/drivers/staging/omapdrm/omap_crtc.c new file mode 100644 index 00000000000..98c263373e8 --- /dev/null +++ b/drivers/staging/omapdrm/omap_crtc.c @@ -0,0 +1,325 @@ +/* + * linux/drivers/staging/omapdrm/omap_crtc.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/omap_drm.h> +#include "omap_drv.h" + +#include "drm_mode.h" +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +#define to_omap_crtc(x) container_of(x, struct omap_crtc, base) + +struct omap_crtc { + struct drm_crtc base; + struct omap_overlay *ovl; + struct omap_overlay_info info; + int id; +}; + +/* push changes down to dss2 */ +static int commit(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct omap_overlay *ovl = omap_crtc->ovl; + struct omap_overlay_info *info = &omap_crtc->info; + int ret; + + DBG("%s", omap_crtc->ovl->name); + DBG("%dx%d -> %dx%d (%d)", info->width, info->height, info->out_width, + info->out_height, info->screen_width); + DBG("%d,%d %p %08x", info->pos_x, info->pos_y, info->vaddr, + info->paddr); + + /* NOTE: do we want to do this at all here, or just wait + * for dpms(ON) since other CRTC's may not have their mode + * set yet, so fb dimensions may still change.. + */ + ret = ovl->set_overlay_info(ovl, info); + if (ret) { + dev_err(dev->dev, "could not set overlay info\n"); + return ret; + } + + /* our encoder doesn't necessarily get a commit() after this, in + * particular in the dpms() and mode_set_base() cases, so force the + * manager to update: + * + * could this be in the encoder somehow? + */ + if (ovl->manager) { + ret = ovl->manager->apply(ovl->manager); + if (ret) { + dev_err(dev->dev, "could not apply\n"); + return ret; + } + } + + if (info->enabled) { + omap_framebuffer_flush(crtc->fb, crtc->x, crtc->y, + crtc->fb->width, crtc->fb->height); + } + + return 0; +} + +/* update parameters that are dependent on the framebuffer dimensions and + * position within the fb that this crtc scans out from. This is called + * when framebuffer dimensions or x,y base may have changed, either due + * to our mode, or a change in another crtc that is scanning out of the + * same fb. + */ +static void update_scanout(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + unsigned long paddr; + void __iomem *vaddr; + int screen_width; + + omap_framebuffer_get_buffer(crtc->fb, crtc->x, crtc->y, + &vaddr, &paddr, &screen_width); + + DBG("%s: %d,%d: %p %08lx (%d)", omap_crtc->ovl->name, + crtc->x, crtc->y, vaddr, paddr, screen_width); + + omap_crtc->info.paddr = paddr; + omap_crtc->info.vaddr = vaddr; + omap_crtc->info.screen_width = screen_width; +} + +static void omap_crtc_gamma_set(struct drm_crtc *crtc, + u16 *red, u16 *green, u16 *blue, uint32_t start, uint32_t size) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->ovl->name); + // XXX ignore? +} + +static void omap_crtc_destroy(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->ovl->name); + drm_crtc_cleanup(crtc); + kfree(omap_crtc); +} + +static void omap_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + DBG("%s: %d", omap_crtc->ovl->name, mode); + + if (mode == DRM_MODE_DPMS_ON) { + update_scanout(crtc); + omap_crtc->info.enabled = true; + } else { + omap_crtc->info.enabled = false; + } + + commit(crtc); +} + +static bool omap_crtc_mode_fixup(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + // XXX I guess we support anything? + DBG("%s", omap_crtc->ovl->name); + return true; +} + +static int omap_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 omap_crtc *omap_crtc = to_omap_crtc(crtc); + + DBG("%s: %d,%d: %dx%d",omap_crtc->ovl->name, x, y, + mode->hdisplay, mode->vdisplay); + + /* just use adjusted mode */ + mode = adjusted_mode; + + omap_crtc->info.width = mode->hdisplay; + omap_crtc->info.height = mode->vdisplay; + omap_crtc->info.out_width = mode->hdisplay; + omap_crtc->info.out_height = mode->vdisplay; + omap_crtc->info.color_mode = OMAP_DSS_COLOR_RGB24U; + omap_crtc->info.rotation_type = OMAP_DSS_ROT_DMA; + omap_crtc->info.rotation = OMAP_DSS_ROT_0; + omap_crtc->info.global_alpha = 0xff; + omap_crtc->info.mirror = 0; + omap_crtc->info.mirror = 0; + omap_crtc->info.pos_x = 0; + omap_crtc->info.pos_y = 0; +#if 0 /* re-enable when these are available in DSS2 driver */ + omap_crtc->info.zorder = 3; /* GUI in the front, video behind */ + omap_crtc->info.min_x_decim = 1; + omap_crtc->info.max_x_decim = 1; + omap_crtc->info.min_y_decim = 1; + omap_crtc->info.max_y_decim = 1; +#endif + + update_scanout(crtc); + + return 0; +} + +static void omap_crtc_prepare(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct omap_overlay *ovl = omap_crtc->ovl; + + DBG("%s", omap_crtc->ovl->name); + + ovl->get_overlay_info(ovl, &omap_crtc->info); + + omap_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); +} + +static void omap_crtc_commit(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->ovl->name); + omap_crtc_dpms(crtc, DRM_MODE_DPMS_ON); +} + +static int omap_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + + DBG("%s %d,%d: fb=%p", omap_crtc->ovl->name, x, y, old_fb); + + update_scanout(crtc); + + return commit(crtc); +} + +static void omap_crtc_load_lut(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + DBG("%s", omap_crtc->ovl->name); +} + +static int omap_crtc_page_flip_locked(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct timeval now; + unsigned long flags; + int ret; + + DBG("%d -> %d", crtc->fb ? crtc->fb->base.id : -1, fb->base.id); + + crtc->fb = fb; + + update_scanout(crtc); + ret = commit(crtc); + + /* wakeup userspace */ + // TODO: this should happen *after* flip.. somehow.. + if (event) { + spin_lock_irqsave(&dev->event_lock, flags); + event->event.sequence = + drm_vblank_count_and_time(dev, omap_crtc->id, &now); + event->event.tv_sec = now.tv_sec; + event->event.tv_usec = now.tv_usec; + list_add_tail(&event->base.link, + &event->base.file_priv->event_list); + wake_up_interruptible(&event->base.file_priv->event_wait); + spin_unlock_irqrestore(&dev->event_lock, flags); + } + + return ret; +} + +int omap_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event) +{ + struct drm_device *dev = crtc->dev; + int ret; + + mutex_lock(&dev->mode_config.mutex); + ret = omap_crtc_page_flip_locked(crtc, fb, event); + mutex_unlock(&dev->mode_config.mutex); + + return ret; +} +EXPORT_SYMBOL(omap_crtc_page_flip); + +static const struct drm_crtc_funcs omap_crtc_funcs = { + .gamma_set = omap_crtc_gamma_set, + .set_config = drm_crtc_helper_set_config, + .destroy = omap_crtc_destroy, + .page_flip = omap_crtc_page_flip_locked, +}; + +static const struct drm_crtc_helper_funcs omap_crtc_helper_funcs = { + .dpms = omap_crtc_dpms, + .mode_fixup = omap_crtc_mode_fixup, + .mode_set = omap_crtc_mode_set, + .prepare = omap_crtc_prepare, + .commit = omap_crtc_commit, + .mode_set_base = omap_crtc_mode_set_base, + .load_lut = omap_crtc_load_lut, +}; + +struct omap_overlay * omap_crtc_get_overlay(struct drm_crtc *crtc) +{ + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + return omap_crtc->ovl; +} + +/* initialize crtc */ +struct drm_crtc * omap_crtc_init(struct drm_device *dev, + struct omap_overlay *ovl, int id) +{ + struct drm_crtc *crtc = NULL; + struct omap_crtc *omap_crtc = kzalloc(sizeof(*omap_crtc), GFP_KERNEL); + + DBG("%s", ovl->name); + + if (!omap_crtc) { + dev_err(dev->dev, "could not allocate CRTC\n"); + goto fail; + } + + omap_crtc->ovl = ovl; + omap_crtc->id = id; + crtc = &omap_crtc->base; + drm_crtc_init(dev, crtc, &omap_crtc_funcs); + drm_crtc_helper_add(crtc, &omap_crtc_helper_funcs); + + return crtc; + +fail: + if (crtc) { + drm_crtc_cleanup(crtc); + kfree(omap_crtc); + } + return NULL; +} diff --git a/drivers/staging/omapdrm/omap_drv.c b/drivers/staging/omapdrm/omap_drv.c new file mode 100644 index 00000000000..fc2d4e9fa92 --- /dev/null +++ b/drivers/staging/omapdrm/omap_drv.c @@ -0,0 +1,751 @@ +/* + * linux/drivers/staging/omapdrm/omap_drv.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/omap_drm.h> +#include "omap_drv.h" + +#include "drm_crtc_helper.h" +#include "drm_fb_helper.h" + +#define DRIVER_NAME MODULE_NAME +#define DRIVER_DESC "OMAP DRM" +#define DRIVER_DATE "20110403" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +struct drm_device *drm_device; + +/* TODO: think about how to handle more than one plugin.. ie. some ops + * me might want to stop on the first plugin that doesn't return an + * error, etc.. + */ +LIST_HEAD(plugin_list); + +/* keep track of whether we are already loaded.. we may need to call + * plugin's load() if they register after we are already loaded + */ +static bool loaded = false; + +/* + * mode config funcs + */ + +/* Notes about mapping DSS and DRM entities: + * CRTC: overlay + * encoder: manager.. with some extension to allow one primary CRTC + * and zero or more video CRTC's to be mapped to one encoder? + * connector: dssdev.. manager can be attached/detached from different + * devices + */ + +static void omap_fb_output_poll_changed(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + DBG("dev=%p", dev); + if (priv->fbdev) { + drm_fb_helper_hotplug_event(priv->fbdev); + } +} + +static struct drm_mode_config_funcs omap_mode_config_funcs = { + .fb_create = omap_framebuffer_create, + .output_poll_changed = omap_fb_output_poll_changed, +}; + +static int get_connector_type(struct omap_dss_device *dssdev) +{ + switch (dssdev->type) { + case OMAP_DISPLAY_TYPE_HDMI: + return DRM_MODE_CONNECTOR_HDMIA; + case OMAP_DISPLAY_TYPE_DPI: + if (!strcmp(dssdev->name, "dvi")) + return DRM_MODE_CONNECTOR_DVID; + default: + return DRM_MODE_CONNECTOR_Unknown; + } +} + +#if 0 /* enable when dss2 supports hotplug */ +static int omap_drm_notifier(struct notifier_block *nb, + unsigned long evt, void *arg) +{ + switch (evt) { + case OMAP_DSS_SIZE_CHANGE: + case OMAP_DSS_HOTPLUG_CONNECT: + case OMAP_DSS_HOTPLUG_DISCONNECT: { + struct drm_device *dev = drm_device; + DBG("hotplug event: evt=%d, dev=%p", evt, dev); + if (dev) { + drm_sysfs_hotplug_event(dev); + } + return NOTIFY_OK; + } + default: /* don't care about other events for now */ + return NOTIFY_DONE; + } +} +#endif + +static void dump_video_chains(void) +{ + int i; + + DBG("dumping video chains: "); + for (i = 0; i < omap_dss_get_num_overlays(); i++) { + struct omap_overlay *ovl = omap_dss_get_overlay(i); + struct omap_overlay_manager *mgr = ovl->manager; + struct omap_dss_device *dssdev = mgr ? mgr->device : NULL; + if (dssdev) { + DBG("%d: %s -> %s -> %s", i, ovl->name, mgr->name, + dssdev->name); + } else if (mgr) { + DBG("%d: %s -> %s", i, ovl->name, mgr->name); + } else { + DBG("%d: %s", i, ovl->name); + } + } +} + +static int omap_modeset_init(struct drm_device *dev) +{ + const struct omap_drm_platform_data *pdata = dev->dev->platform_data; + struct omap_drm_private *priv = dev->dev_private; + struct omap_dss_device *dssdev = NULL; + int i, j; + unsigned int connected_connectors = 0; + + /* create encoders for each manager */ + int create_encoder(int i) { + struct omap_overlay_manager *mgr = + omap_dss_get_overlay_manager(i); + struct drm_encoder *encoder = omap_encoder_init(dev, mgr); + + if (!encoder) { + dev_err(dev->dev, "could not create encoder\n"); + return -ENOMEM; + } + + priv->encoders[priv->num_encoders++] = encoder; + + return 0; + } + + /* create connectors for each display device */ + int create_connector(struct omap_dss_device *dssdev) { + static struct notifier_block *notifier; + struct drm_connector *connector; + + if (!dssdev->driver) { + dev_warn(dev->dev, "%s has no driver.. skipping it\n", + dssdev->name); + return 0; + } + + if (!(dssdev->driver->get_timings || + dssdev->driver->get_edid)) { + dev_warn(dev->dev, "%s driver does not support " + "get_timings or get_edid.. skipping it!\n", + dssdev->name); + return 0; + } + + omap_dss_get_device(dssdev); + + connector = omap_connector_init(dev, + get_connector_type(dssdev), dssdev); + + if (!connector) { + dev_err(dev->dev, "could not create connector\n"); + return -ENOMEM; + } + + /* track what is already connected.. rather than looping thru + * all connectors twice later, first for connected then for + * remainder (which could be a race condition if connected + * status changes) + */ + if (omap_connector_detect(connector, true) == + connector_status_connected) { + connected_connectors |= (1 << priv->num_connectors); + } + + priv->connectors[priv->num_connectors++] = connector; + +#if 0 /* enable when dss2 supports hotplug */ + notifier = kzalloc(sizeof(struct notifier_block), GFP_KERNEL); + notifier->notifier_call = omap_drm_notifier; + omap_dss_add_notify(dssdev, notifier); +#else + notifier = NULL; +#endif + + for (j = 0; j < priv->num_encoders; j++) { + struct omap_overlay_manager *mgr = + omap_encoder_get_manager(priv->encoders[j]); + if (mgr->device == dssdev) { + drm_mode_connector_attach_encoder(connector, + priv->encoders[j]); + } + } + + return 0; + } + + /* create up to max_overlays CRTCs mapping to overlays.. by default, + * connect the overlays to different managers/encoders, giving priority + * to encoders connected to connectors with a detected connection + */ + int create_crtc(int i) { + struct omap_overlay *ovl = omap_dss_get_overlay(i); + struct omap_overlay_manager *mgr = NULL; + struct drm_crtc *crtc; + + if (ovl->manager) { + DBG("disconnecting %s from %s", ovl->name, + ovl->manager->name); + ovl->unset_manager(ovl); + } + + /* find next best connector, ones with detected connection first + */ + while (j < priv->num_connectors && !mgr) { + if (connected_connectors & (1 << j)) { + struct drm_encoder * encoder = + omap_connector_attached_encoder( + priv->connectors[j]); + if (encoder) { + mgr = omap_encoder_get_manager(encoder); + } + } + j++; + } + + /* if we couldn't find another connected connector, lets start + * looking at the unconnected connectors: + */ + while (j < 2 * priv->num_connectors && !mgr) { + int idx = j - priv->num_connectors; + if (!(connected_connectors & (1 << idx))) { + struct drm_encoder * encoder = + omap_connector_attached_encoder( + priv->connectors[idx]); + if (encoder) { + mgr = omap_encoder_get_manager(encoder); + } + } + j++; + } + + if (mgr) { + DBG("connecting %s to %s", ovl->name, mgr->name); + ovl->set_manager(ovl, mgr); + } + + crtc = omap_crtc_init(dev, ovl, priv->num_crtcs); + + if (!crtc) { + dev_err(dev->dev, "could not create CRTC\n"); + return -ENOMEM; + } + + priv->crtcs[priv->num_crtcs++] = crtc; + + return 0; + } + + drm_mode_config_init(dev); + + if (pdata) { + /* if platform data is provided by the board file, use it to + * control which overlays, managers, and devices we own. + */ + for (i = 0; i < pdata->mgr_cnt; i++) { + create_encoder(pdata->mgr_ids[i]); + } + + for (i = 0; i < pdata->dev_cnt; i++) { + int m(struct omap_dss_device *dssdev, void *data) { + return ! strcmp(dssdev->name, data); + } + struct omap_dss_device *dssdev = + omap_dss_find_device( + (void *)pdata->dev_names[i], m); + if (!dssdev) { + dev_warn(dev->dev, "no such dssdev: %s\n", + pdata->dev_names[i]); + continue; + } + create_connector(dssdev); + } + + j = 0; + for (i = 0; i < pdata->ovl_cnt; i++) { + create_crtc(pdata->ovl_ids[i]); + } + } else { + /* otherwise just grab up to CONFIG_DRM_OMAP_NUM_CRTCS and try + * to make educated guesses about everything else + */ + int max_overlays = min(omap_dss_get_num_overlays(), + CONFIG_DRM_OMAP_NUM_CRTCS); + + for (i = 0; i < omap_dss_get_num_overlay_managers(); i++) { + create_encoder(i); + } + + for_each_dss_dev(dssdev) { + create_connector(dssdev); + } + + j = 0; + for (i = 0; i < max_overlays; i++) { + create_crtc(i); + } + } + + /* for now keep the mapping of CRTCs and encoders static.. */ + for (i = 0; i < priv->num_encoders; i++) { + struct drm_encoder *encoder = priv->encoders[i]; + struct omap_overlay_manager *mgr = + omap_encoder_get_manager(encoder); + + encoder->possible_crtcs = 0; + + for (j = 0; j < priv->num_crtcs; j++) { + struct omap_overlay *ovl = + omap_crtc_get_overlay(priv->crtcs[j]); + if (ovl->manager == mgr) { + encoder->possible_crtcs |= (1 << j); + } + } + + DBG("%s: possible_crtcs=%08x", mgr->name, + encoder->possible_crtcs); + } + + dump_video_chains(); + + dev->mode_config.min_width = 640; + dev->mode_config.min_height = 480; + + /* note: pvr can't currently handle dst surfaces larger than 2k by 2k */ + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + + dev->mode_config.funcs = &omap_mode_config_funcs; + + return 0; +} + +/* + * drm driver funcs + */ + +/** + * load - setup chip and create an initial config + * @dev: DRM device + * @flags: startup flags + * + * The driver load routine has to do several things: + * - initialize the memory manager + * - allocate initial config memory + * - setup the DRM framebuffer with the allocated memory + */ +static int dev_load(struct drm_device *dev, unsigned long flags) +{ + struct omap_drm_private *priv; + struct omap_drm_plugin *plugin; + int ret; + + DBG("load: dev=%p", dev); + + drm_device = dev; + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + dev_err(dev->dev, "could not allocate priv\n"); + return -1; + } + + dev->dev_private = priv; + + ret = omap_modeset_init(dev); + if (ret) { + dev_err(dev->dev, "omap_modeset_init failed: ret=%d\n", ret); + // hmm + //return ret; + } + + priv->fbdev = omap_fbdev_init(dev); + if (!priv->fbdev) { + dev_err(dev->dev, "omap_fbdev_init failed\n"); + ret = -ENOMEM; + // hmm + //return ret; + } + + drm_kms_helper_poll_init(dev); + + ret = drm_vblank_init(dev, priv->num_crtcs); + if (ret) { + dev_err(dev->dev, "could not init vblank\n"); + } + + loaded = true; + + list_for_each_entry(plugin, &plugin_list, list) { + ret = plugin->load(dev, flags); + } + + return 0; +} + +static int dev_unload(struct drm_device *dev) +{ + struct omap_drm_plugin *plugin; + int ret; + + DBG("unload: dev=%p", dev); + + list_for_each_entry(plugin, &plugin_list, list) { + ret = plugin->unload(dev); + } + + drm_kms_helper_poll_fini(dev); + drm_vblank_cleanup(dev); + + loaded = false; + + return 0; +} + +static int dev_open(struct drm_device *dev, struct drm_file *file) +{ + struct omap_drm_plugin *plugin; + bool found_pvr = false; + int ret; + + file->driver_priv = NULL; + + DBG("open: dev=%p, file=%p", dev, file); + + list_for_each_entry(plugin, &plugin_list, list) { + if (!strcmp(DRIVER_NAME "_pvr", plugin->name)) { + found_pvr = true; + break; + } + } + + if (!found_pvr) { + DBG("open: PVR submodule not loaded.. let's try now"); + request_module(DRIVER_NAME "_pvr"); + } + + list_for_each_entry(plugin, &plugin_list, list) { + ret = plugin->open(dev, file); + } + + return 0; +} + +static int dev_firstopen(struct drm_device *dev) +{ + DBG("firstopen: dev=%p", dev); + return 0; +} + +/** + * lastclose - clean up after all DRM clients have exited + * @dev: DRM device + * + * Take care of cleaning up after all DRM clients have exited. In the + * mode setting case, we want to restore the kernel's initial mode (just + * in case the last client left us in a bad state). + * + * Additionally, in the non-mode setting case, we'll tear down the AGP + * and DMA structures, since the kernel won't be using them, and clean + * up any GEM state. + */ +static void dev_lastclose(struct drm_device * dev) +{ + DBG("lastclose: dev=%p", dev); +} + +static void dev_preclose(struct drm_device * dev, struct drm_file *file) +{ + DBG("preclose: dev=%p", dev); +} + +static void dev_postclose(struct drm_device *dev, struct drm_file *file) +{ + struct omap_drm_plugin *plugin; + int ret; + + DBG("postclose: dev=%p, file=%p", dev, file); + + list_for_each_entry(plugin, &plugin_list, list) { + ret = plugin->release(dev, file); + } + + return; +} + +/** + * enable_vblank - enable vblank interrupt events + * @dev: DRM device + * @crtc: which irq to enable + * + * Enable vblank interrupts for @crtc. If the device doesn't have + * a hardware vblank counter, this routine should be a no-op, since + * interrupts will have to stay on to keep the count accurate. + * + * RETURNS + * Zero on success, appropriate errno if the given @crtc's vblank + * interrupt cannot be enabled. + */ +static int dev_enable_vblank(struct drm_device *dev, int crtc) +{ + DBG("enable_vblank: dev=%p, crtc=%d", dev, crtc); + return 0; +} + +/** + * disable_vblank - disable vblank interrupt events + * @dev: DRM device + * @crtc: which irq to enable + * + * Disable vblank interrupts for @crtc. If the device doesn't have + * a hardware vblank counter, this routine should be a no-op, since + * interrupts will have to stay on to keep the count accurate. + */ +static void dev_disable_vblank(struct drm_device *dev, int crtc) +{ + DBG("disable_vblank: dev=%p, crtc=%d", dev, crtc); +} + +/** + * Called by \c drm_device_is_agp. Typically used to determine if a + * card is really attached to AGP or not. + * + * \param dev DRM device handle + * + * \returns + * One of three values is returned depending on whether or not the + * card is absolutely \b not AGP (return of 0), absolutely \b is AGP + * (return of 1), or may or may not be AGP (return of 2). + */ +static int dev_device_is_agp(struct drm_device *dev) +{ + return 0; +} + +static irqreturn_t dev_irq_handler(DRM_IRQ_ARGS) +{ + return IRQ_HANDLED; +} + +static void dev_irq_preinstall(struct drm_device *dev) +{ + DBG("irq_preinstall: dev=%p", dev); +} + +static int dev_irq_postinstall(struct drm_device *dev) +{ + DBG("irq_postinstall: dev=%p", dev); + return 0; +} + +static void dev_irq_uninstall(struct drm_device *dev) +{ + DBG("irq_uninstall: dev=%p", dev); +} + +static int fop_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct omap_drm_plugin *plugin; + int ret = 0; + + list_for_each_entry(plugin, &plugin_list, list) { + ret = plugin->mmap(file, vma); + if (!ret) { + /* on first plugin that succeeds, bail out of iteration */ + return ret; + } + } + + return ret; +} + +struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = {{0}}; + +static struct drm_driver omap_drm_driver = { + .driver_features = DRIVER_HAVE_IRQ | DRIVER_MODESET, + .load = dev_load, + .unload = dev_unload, + .open = dev_open, + .firstopen = dev_firstopen, + .lastclose = dev_lastclose, + .preclose = dev_preclose, + .postclose = dev_postclose, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = dev_enable_vblank, + .disable_vblank = dev_disable_vblank, + .device_is_agp = dev_device_is_agp, + .irq_preinstall = dev_irq_preinstall, + .irq_postinstall = dev_irq_postinstall, + .irq_uninstall = dev_irq_uninstall, + .irq_handler = dev_irq_handler, + .reclaim_buffers = drm_core_reclaim_buffers, + .ioctls = ioctls, + .num_ioctls = 0, + .fops = { + .owner = THIS_MODULE, + .open = drm_open, + .unlocked_ioctl = drm_ioctl, + .release = drm_release, + .mmap = fop_mmap, + .poll = drm_poll, + .fasync = drm_fasync, + .read = drm_read, + }, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +int omap_drm_register_plugin(struct omap_drm_plugin *plugin) +{ + struct drm_device *dev = drm_device; + int i; + + DBG("register plugin: %p (%s)", plugin, plugin->name); + + /* XXX: PVR code uses drm_file->driver_priv... + * need to come up with some sane way to handle this. + */ + + list_add_tail(&plugin->list, &plugin_list); + + /* register the plugin's ioctl's */ + for (i = 0; i < plugin->num_ioctls; i++) { + int nr = i + plugin->ioctl_start; + + /* check for out of bounds ioctl nr or already registered ioctl */ + if (nr > ARRAY_SIZE(ioctls) || ioctls[nr].func) { + dev_err(dev->dev, "invalid ioctl: %d (nr=%d)\n", i, nr); + return -EINVAL; + } + + DBG("register ioctl: %d %08x", nr, plugin->ioctls[i].cmd); + + ioctls[nr] = plugin->ioctls[i]; + + if (nr >= omap_drm_driver.num_ioctls) { + omap_drm_driver.num_ioctls = nr + 1; + } + } + + if (loaded) { + plugin->load(dev, 0); + } + + return 0; +} +EXPORT_SYMBOL(omap_drm_register_plugin); + +int omap_drm_unregister_plugin(struct omap_drm_plugin *plugin) +{ + list_del(&plugin->list); + return 0; +} +EXPORT_SYMBOL(omap_drm_unregister_plugin); + +struct drm_framebuffer * omap_drm_get_default_fb(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + return priv->fbdev->fb; +} +EXPORT_SYMBOL(omap_drm_get_default_fb); + +static int pdev_suspend(struct platform_device *pDevice, pm_message_t state) +{ + DBG(""); + return 0; +} + +static int pdev_resume(struct platform_device *device) +{ + DBG(""); + return 0; +} + +static void pdev_shutdown(struct platform_device *device) +{ + DBG(""); +} + +static int pdev_probe(struct platform_device *device) +{ + DBG("%s", device->name); + return drm_platform_init(&omap_drm_driver, device); +} + +static int pdev_remove(struct platform_device *device) +{ + DBG(""); + drm_platform_exit(&omap_drm_driver, device); + return 0; +} + +struct platform_driver pdev = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .probe = pdev_probe, + .remove = pdev_remove, + .suspend = pdev_suspend, + .resume = pdev_resume, + .shutdown = pdev_shutdown, +}; + +static int __init omap_drm_init(void) +{ + DBG("init"); + return platform_driver_register(&pdev); +} + +static void __exit omap_drm_fini(void) +{ + DBG("fini"); + platform_driver_unregister(&pdev); +} + +/* need late_initcall() so we load after dss_driver's are loaded */ +late_initcall(omap_drm_init); +module_exit(omap_drm_fini); + +MODULE_AUTHOR("Rob Clark <rob@ti.com>"); +MODULE_DESCRIPTION("OMAP DRM Display Driver"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/staging/omapdrm/omap_drv.h b/drivers/staging/omapdrm/omap_drv.h new file mode 100644 index 00000000000..3a64d5254ea --- /dev/null +++ b/drivers/staging/omapdrm/omap_drv.h @@ -0,0 +1,68 @@ +/* + * linux/drivers/staging/omapdrm/omap_drv.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP_DRV_H__ +#define __OMAP_DRV_H__ + +#include <video/omapdss.h> +#include <linux/module.h> + +#define DBG(fmt,...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) +#define VERB(fmt,...) if (0) DRM_DEBUG(fmt, ##__VA_ARGS__) /* verbose debug */ + +#define MODULE_NAME "omapdrm" + +struct omap_drm_private { + int num_crtcs; + struct drm_crtc *crtcs[8]; + int num_encoders; + struct drm_encoder *encoders[8]; + int num_connectors; + struct drm_connector *connectors[8]; + + struct drm_fb_helper *fbdev; +}; + +struct drm_fb_helper * omap_fbdev_init(struct drm_device *dev); + +struct drm_crtc * omap_crtc_init(struct drm_device *dev, + struct omap_overlay *ovl, int id); +struct omap_overlay * omap_crtc_get_overlay(struct drm_crtc *crtc); + +struct drm_encoder * omap_encoder_init(struct drm_device *dev, + struct omap_overlay_manager *mgr); +struct omap_overlay_manager * omap_encoder_get_manager( + struct drm_encoder *encoder); +struct drm_encoder * omap_connector_attached_encoder ( + struct drm_connector *connector); +enum drm_connector_status omap_connector_detect( + struct drm_connector *connector, bool force); + +struct drm_connector * omap_connector_init(struct drm_device *dev, + int connector_type, struct omap_dss_device *dssdev); +void omap_connector_mode_set(struct drm_connector *connector, + struct drm_display_mode *mode); +void omap_connector_flush(struct drm_connector *connector, + int x, int y, int w, int h); +void omap_connector_dpms(struct drm_connector *connector, int mode); + +struct drm_framebuffer * omap_framebuffer_create(struct drm_device *dev, + struct drm_file *file, struct drm_mode_fb_cmd *mode_cmd); + +#endif /* __OMAP_DRV_H__ */ diff --git a/drivers/staging/omapdrm/omap_encoder.c b/drivers/staging/omapdrm/omap_encoder.c new file mode 100644 index 00000000000..810c7e244fa --- /dev/null +++ b/drivers/staging/omapdrm/omap_encoder.c @@ -0,0 +1,198 @@ +/* + * linux/drivers/staging/omapdrm/omap_encoder.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/omap_drm.h> +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + +/* + * encoder funcs + */ + +#define to_omap_encoder(x) container_of(x, struct omap_encoder, base) + +struct omap_encoder { + struct drm_encoder base; + struct omap_overlay_manager *mgr; +}; + +static void omap_encoder_destroy(struct drm_encoder *encoder) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + DBG("%s", omap_encoder->mgr->name); + drm_encoder_cleanup(encoder); + kfree(omap_encoder); +} + +static void omap_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct omap_drm_private *priv = dev->dev_private; + int i; + + DBG("%s: %d", omap_encoder->mgr->name, mode); + + /* managers don't need to do anything for DPMS.. but we do + * need to propagate to the connector, who is actually going + * to enable/disable as needed: + */ + for (i = 0; i < priv->num_connectors; i++) { + struct drm_connector *connector = priv->connectors[i]; + if (connector->encoder == encoder) { + omap_connector_dpms(connector, mode); + } + } +} + +static bool omap_encoder_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + DBG("%s", omap_encoder->mgr->name); + return true; +} + +static void omap_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + struct drm_device *dev = encoder->dev; + struct omap_drm_private *priv = dev->dev_private; + int i; + + mode = adjusted_mode; + + DBG("%s: set mode: %dx%d", omap_encoder->mgr->name, + mode->hdisplay, mode->vdisplay); + + for (i = 0; i < priv->num_connectors; i++) { + struct drm_connector *connector = priv->connectors[i]; + if (connector->encoder == encoder) { + omap_connector_mode_set(connector, mode); + } + } +} + +static void omap_encoder_prepare(struct drm_encoder *encoder) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + struct drm_encoder_helper_funcs *encoder_funcs = + encoder->helper_private; + DBG("%s", omap_encoder->mgr->name); + encoder_funcs->dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void omap_encoder_commit(struct drm_encoder *encoder) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + struct drm_encoder_helper_funcs *encoder_funcs = + encoder->helper_private; + DBG("%s", omap_encoder->mgr->name); + omap_encoder->mgr->apply(omap_encoder->mgr); + encoder_funcs->dpms(encoder, DRM_MODE_DPMS_ON); +} + +static const struct drm_encoder_funcs omap_encoder_funcs = { + .destroy = omap_encoder_destroy, +}; + +static const struct drm_encoder_helper_funcs omap_encoder_helper_funcs = { + .dpms = omap_encoder_dpms, + .mode_fixup = omap_encoder_mode_fixup, + .mode_set = omap_encoder_mode_set, + .prepare = omap_encoder_prepare, + .commit = omap_encoder_commit, +}; + +struct omap_overlay_manager * omap_encoder_get_manager( + struct drm_encoder *encoder) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + return omap_encoder->mgr; +} + +/* maybe this could go away and we just use drm_vblank_wait()? */ +int omap_encoder_wait_for_vsync(struct drm_encoder *encoder) +{ + struct omap_encoder *omap_encoder = to_omap_encoder(encoder); + DBG("%s", omap_encoder->mgr->name); + return omap_encoder->mgr->wait_for_vsync(omap_encoder->mgr); +} +EXPORT_SYMBOL(omap_encoder_wait_for_vsync); + +/* initialize encoder */ +struct drm_encoder * omap_encoder_init(struct drm_device *dev, + struct omap_overlay_manager *mgr) +{ + struct drm_encoder *encoder = NULL; + struct omap_encoder *omap_encoder; + struct omap_overlay_manager_info info; + int ret; + + DBG("%s", mgr->name); + + omap_encoder = kzalloc(sizeof(*omap_encoder), GFP_KERNEL); + if (!omap_encoder) { + dev_err(dev->dev, "could not allocate encoder\n"); + goto fail; + } + + omap_encoder->mgr = mgr; + encoder = &omap_encoder->base; + + drm_encoder_init(dev, encoder, &omap_encoder_funcs, + DRM_MODE_ENCODER_TMDS); + drm_encoder_helper_add(encoder, &omap_encoder_helper_funcs); + + mgr->get_manager_info(mgr, &info); + + /* TODO: fix hard-coded setup.. */ + info.default_color = 0x00000000; + info.trans_key = 0x00000000; + info.trans_key_type = OMAP_DSS_COLOR_KEY_GFX_DST; + info.trans_enabled = false; + info.alpha_enabled = true; + + ret = mgr->set_manager_info(mgr, &info); + if (ret) { + dev_err(dev->dev, "could not set manager info\n"); + goto fail; + } + + ret = mgr->apply(mgr); + if (ret) { + dev_err(dev->dev, "could not apply\n"); + goto fail; + } + + return encoder; + +fail: + if (encoder) { + drm_encoder_cleanup(encoder); + kfree(omap_encoder); + } + + return NULL; +} diff --git a/drivers/staging/omapdrm/omap_fb.c b/drivers/staging/omapdrm/omap_fb.c new file mode 100644 index 00000000000..c93ec2e9e69 --- /dev/null +++ b/drivers/staging/omapdrm/omap_fb.c @@ -0,0 +1,251 @@ +/* + * linux/drivers/staging/omapdrm/omap_fb.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <plat/vram.h> + +#include <linux/omap_drm.h> +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + + +static char *def_vram; +module_param_named(vram, def_vram, charp, 0); + +/* + * framebuffer funcs + */ + +#define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) + +struct omap_framebuffer { + struct drm_framebuffer base; + + /* framebuffer size/phys-addr/virt-addr */ + int size; + unsigned long paddr; + void __iomem *vaddr; +}; + +static int omap_framebuffer_create_handle(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned int *handle) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + DBG("framebuffer: get handle: %p", omap_fb); + + // TODO, I suppose this really should be some sort of GEM handle + // to the framebuffer object, in case it needs to be mapped or + // something. Right now this will go-exist badly with PVR, who + // implements the mmap() fxn.. need to think about how to handle + // this.. + + *handle = 42; + + return 0; +} + +static void omap_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + + DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); + + drm_framebuffer_cleanup(fb); + + if (omap_fb->vaddr) { + iounmap(omap_fb->vaddr); + } + + /* drm framework should, but doesn't (as of 2.6.35) disconnect any + * CRTCs connected to this fb before destroying it.. so could be + * some small window when garbage is seen on screen. But in + * practice, unlikely because we have a private vram pool. So I + * won't worry too much about it. + */ + if (omap_fb->paddr) { + omap_vram_free(omap_fb->paddr, omap_fb->size); + } + + kfree(omap_fb); +} + +static int omap_framebuffer_dirty(struct drm_framebuffer *fb, + struct drm_file *file_priv, unsigned flags, unsigned color, + struct drm_clip_rect *clips, unsigned num_clips) +{ + int i; + + for (i = 0; i < num_clips; i++) { + omap_framebuffer_flush(fb, clips[i].x1, clips[i].y1, + clips[i].x2 - clips[i].x1, + clips[i].y2 - clips[i].y1); + } + + return 0; +} + +static const struct drm_framebuffer_funcs omap_framebuffer_funcs = { + .create_handle = omap_framebuffer_create_handle, + .destroy = omap_framebuffer_destroy, + .dirty = omap_framebuffer_dirty, +}; + +int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, + void **vaddr, unsigned long *paddr, int *screen_width) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + int bpp = 4; //XXX fb->depth / 8; + unsigned long offset; + + offset = (x * bpp) + (y * fb->pitch); + + *vaddr = omap_fb->vaddr + offset; + *paddr = omap_fb->paddr + offset; + *screen_width = fb->pitch / bpp; + + return omap_fb->size; +} +EXPORT_SYMBOL(omap_framebuffer_get_buffer); + +/* iterate thru all the connectors, returning ones that are attached + * to the same fb.. + */ +struct drm_connector * omap_framebuffer_get_next_connector( + struct drm_framebuffer *fb, struct drm_connector *from) +{ + struct drm_device *dev = fb->dev; + struct list_head *connector_list = &dev->mode_config.connector_list; + struct drm_connector *connector = from; + + if (!from) { + return list_first_entry(connector_list, typeof(*from), head); + } + + list_for_each_entry_from(connector, connector_list, head) { + if (connector != from) { + struct drm_encoder *encoder = connector->encoder; + struct drm_crtc *crtc = encoder ? encoder->crtc : NULL; + if (crtc && crtc->fb == fb) { + return connector; + } + } + } + + return NULL; +} +EXPORT_SYMBOL(omap_framebuffer_get_next_connector); + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +void omap_framebuffer_flush(struct drm_framebuffer *fb, + int x, int y, int w, int h) +{ + struct drm_connector *connector = NULL; + + VERB("flush: %d,%d %dx%d, fb=%p", x, y, w, h, fb); + + while ((connector = omap_framebuffer_get_next_connector(fb, connector))) { + /* only consider connectors that are part of a chain */ + if (connector->encoder && connector->encoder->crtc) { + /* TODO: maybe this should propagate thru the crtc who + * could do the coordinate translation.. + */ + struct drm_crtc *crtc = connector->encoder->crtc; + int cx = max(0, x - crtc->x); + int cy = max(0, y - crtc->y); + int cw = w + (x - crtc->x) - cx; + int ch = h + (y - crtc->y) - cy; + + omap_connector_flush(connector, cx, cy, cw, ch); + } + } +} +EXPORT_SYMBOL(omap_framebuffer_flush); + +struct drm_framebuffer * omap_framebuffer_create(struct drm_device *dev, + struct drm_file *file, struct drm_mode_fb_cmd *mode_cmd) +{ + return omap_framebuffer_init(dev, mode_cmd); +} + +struct drm_framebuffer * omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd *mode_cmd) +{ + struct omap_framebuffer *omap_fb; + struct drm_framebuffer *fb = NULL; + unsigned long paddr; + int size, ret; + + /* in case someone tries to feed us a completely bogus stride: */ + mode_cmd->pitch = max(mode_cmd->pitch, + mode_cmd->width * mode_cmd->bpp / 8); + + /* pvr needs to have a stride that is a multiple of 8 pixels: */ + mode_cmd->pitch = ALIGN(mode_cmd->pitch, 8 * (mode_cmd->bpp / 8)); + + DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d)", dev, + mode_cmd, mode_cmd->width, mode_cmd->height); + + omap_fb = kzalloc(sizeof(*omap_fb), GFP_KERNEL); + if (!omap_fb) { + dev_err(dev->dev, "could not allocate fb\n"); + goto fail; + } + + fb = &omap_fb->base; + ret = drm_framebuffer_init(dev, fb, &omap_framebuffer_funcs); + if (ret) { + dev_err(dev->dev, "framebuffer init failed: %d\n", ret); + goto fail; + } + + DBG("create: FB ID: %d (%p)", fb->base.id, fb); + + size = PAGE_ALIGN(mode_cmd->pitch * mode_cmd->height); + + DBG("allocating %d bytes for fb %d", size, dev->primary->index); + ret = omap_vram_alloc(OMAP_VRAM_MEMTYPE_SDRAM, size, &paddr); + if (ret) { + dev_err(dev->dev, "failed to allocate vram\n"); + goto fail; + } + + omap_fb->size = size; + omap_fb->paddr = paddr; + omap_fb->vaddr = ioremap_wc(paddr, size); + + if (!omap_fb->vaddr) { + dev_err(dev->dev, "failed to ioremap framebuffer\n"); + goto fail; + } + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + + return fb; + +fail: + if (fb) { + omap_framebuffer_destroy(fb); + } + return NULL; +} +EXPORT_SYMBOL(omap_framebuffer_init); diff --git a/drivers/staging/omapdrm/omap_fbdev.c b/drivers/staging/omapdrm/omap_fbdev.c new file mode 100644 index 00000000000..205bc32b914 --- /dev/null +++ b/drivers/staging/omapdrm/omap_fbdev.c @@ -0,0 +1,279 @@ +/* + * linux/drivers/staging/omapdrm/omap_fbdev.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/omap_drm.h> +#include "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_fb_helper.h" + +/* + * fbdev funcs, to implement legacy fbdev interface on top of drm driver + */ + +#define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base) + +struct omap_fbdev { + struct drm_fb_helper base; + struct drm_framebuffer *fb; +}; + +static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h); + +static ssize_t omap_fbdev_write(struct fb_info *fbi, const char __user *buf, + size_t count, loff_t *ppos) +{ + ssize_t res; + + res = fb_sys_write(fbi, buf, count, ppos); + omap_fbdev_flush(fbi, 0, 0, fbi->var.xres, fbi->var.yres); + + return res; +} + +static void omap_fbdev_fillrect(struct fb_info *fbi, + const struct fb_fillrect *rect) +{ + sys_fillrect(fbi, rect); + omap_fbdev_flush(fbi, rect->dx, rect->dy, rect->width, rect->height); +} + +static void omap_fbdev_copyarea(struct fb_info *fbi, + const struct fb_copyarea *area) +{ + sys_copyarea(fbi, area); + omap_fbdev_flush(fbi, area->dx, area->dy, area->width, area->height); +} + +static void omap_fbdev_imageblit(struct fb_info *fbi, + const struct fb_image *image) +{ + sys_imageblit(fbi, image); + omap_fbdev_flush(fbi, image->dx, image->dy, + image->width, image->height); +} + +static struct fb_ops omap_fb_ops = { + .owner = THIS_MODULE, + + /* Note: to properly handle manual update displays, we wrap the + * basic fbdev ops which write to the framebuffer + */ + .fb_read = fb_sys_read, + .fb_write = omap_fbdev_write, + .fb_fillrect = omap_fbdev_fillrect, + .fb_copyarea = omap_fbdev_copyarea, + .fb_imageblit = omap_fbdev_imageblit, + + .fb_check_var = drm_fb_helper_check_var, + .fb_set_par = drm_fb_helper_set_par, + .fb_pan_display = drm_fb_helper_pan_display, + .fb_blank = drm_fb_helper_blank, + .fb_setcmap = drm_fb_helper_setcmap, + + .fb_debug_enter = drm_fb_helper_debug_enter, + .fb_debug_leave = drm_fb_helper_debug_leave, +}; + +static int omap_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct omap_fbdev *fbdev = to_omap_fbdev(helper); + struct drm_device *dev = helper->dev; + struct drm_framebuffer *fb; + struct fb_info *fbi; + struct drm_mode_fb_cmd mode_cmd = {0}; + unsigned long paddr; + void __iomem *vaddr; + int size, screen_width; + int ret; + + /* only doing ARGB32 since this is what is needed to alpha-blend + * with video overlays: + */ + sizes->surface_bpp = 32; + sizes->surface_depth = 32; + + DBG("create fbdev: %dx%d@%d", sizes->surface_width, + sizes->surface_height, sizes->surface_bpp); + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + + mode_cmd.bpp = sizes->surface_bpp; + mode_cmd.depth = sizes->surface_depth; + + mutex_lock(&dev->struct_mutex); + + fbi = framebuffer_alloc(0, dev->dev); + if (!fbi) { + dev_err(dev->dev, "failed to allocate fb info\n"); + ret = -ENOMEM; + goto fail; + } + + DBG("fbi=%p, dev=%p", fbi, dev); + + fbdev->fb = omap_framebuffer_init(dev, &mode_cmd); + if (!fbdev->fb) { + dev_err(dev->dev, "failed to allocate fb\n"); + ret = -ENOMEM; + goto fail; + } + + fb = fbdev->fb; + helper->fb = fb; + helper->fbdev = fbi; + + fbi->par = helper; + fbi->flags = FBINFO_DEFAULT; + fbi->fbops = &omap_fb_ops; + + strcpy(fbi->fix.id, MODULE_NAME); + + ret = fb_alloc_cmap(&fbi->cmap, 256, 0); + if (ret) { + ret = -ENOMEM; + goto fail; + } + + drm_fb_helper_fill_fix(fbi, fb->pitch, fb->depth); + drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height); + + size = omap_framebuffer_get_buffer(fb, 0, 0, + &vaddr, &paddr, &screen_width); + + dev->mode_config.fb_base = paddr; + + fbi->screen_base = vaddr; + fbi->screen_size = size; + fbi->fix.smem_start = paddr; + fbi->fix.smem_len = size; + + DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); + DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); + + mutex_unlock(&dev->struct_mutex); + + return 0; + +fail: + mutex_unlock(&dev->struct_mutex); + // TODO cleanup? + return ret; +} + +static void omap_crtc_fb_gamma_set(struct drm_crtc *crtc, + u16 red, u16 green, u16 blue, int regno) +{ + DBG("fbdev: set gamma"); + // XXX ignore? +} + +static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc, + u16 *red, u16 *green, u16 *blue, int regno) +{ + DBG("fbdev: get gamma"); + // XXX ignore? +} + +static int omap_fbdev_probe(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + int new_fb = 0; + int ret; + + if (!helper->fb) { + ret = omap_fbdev_create(helper, sizes); + if (ret) + return ret; + new_fb = 1; + } + return new_fb; +} + +static struct drm_fb_helper_funcs omap_fb_helper_funcs = { + .gamma_set = omap_crtc_fb_gamma_set, + .gamma_get = omap_crtc_fb_gamma_get, + .fb_probe = omap_fbdev_probe, +}; + +static struct drm_fb_helper * get_fb(struct fb_info *fbi) +{ + if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { + /* these are not the fb's you're looking for */ + return NULL; + } + return fbi->par; +} + +/* flush an area of the framebuffer (in case of manual update display that + * is not automatically flushed) + */ +static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h) +{ + struct drm_fb_helper *helper = get_fb(fbi); + + if (!helper) + return; + + VERB("flush fbdev: %d,%d %dx%d, fbi=%p", x, y, w, h, fbi); + + omap_framebuffer_flush(helper->fb, x, y, w, h); +} +EXPORT_SYMBOL(omap_fbdev_flush); + +/* initialize fbdev helper */ +struct drm_fb_helper * omap_fbdev_init(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct omap_fbdev *fbdev = NULL; + struct drm_fb_helper *helper; + int ret = 0; + + fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) { + dev_err(dev->dev, "could not allocate fbdev\n"); + goto fail; + } + + helper = &fbdev->base; + + helper->funcs = &omap_fb_helper_funcs; + + ret = drm_fb_helper_init(dev, helper, + priv->num_crtcs, priv->num_connectors); + if (ret) { + dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); + goto fail; + } + + drm_fb_helper_single_add_all_connectors(helper); + drm_fb_helper_initial_config(helper, 32); + + priv->fbdev = helper; + + return helper; + +fail: + if (fbdev) { + kfree(fbdev); + } + return NULL; +} diff --git a/drivers/video/omap2/omapfb/Kconfig b/drivers/video/omap2/omapfb/Kconfig index aa33386c81f..6c948411bbe 100644 --- a/drivers/video/omap2/omapfb/Kconfig +++ b/drivers/video/omap2/omapfb/Kconfig @@ -1,6 +1,6 @@ menuconfig FB_OMAP2 tristate "OMAP2+ frame buffer support (EXPERIMENTAL)" - depends on FB && OMAP2_DSS + depends on FB && OMAP2_DSS && !DRM_OMAP select OMAP2_VRAM select OMAP2_VRFB if ARCH_OMAP2 || ARCH_OMAP3 @@ -8,7 +8,7 @@ menuconfig FB_OMAP2 select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT help - Frame buffer driver for OMAP2+ based boards. + Legacy frame buffer driver for OMAP2/3 based boards. config FB_OMAP2_DEBUG_SUPPORT bool "Debug support for OMAP2+ FB" diff --git a/include/linux/Kbuild b/include/linux/Kbuild index 01f63627505..ca53772495d 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -280,6 +280,7 @@ header-y += nubus.h header-y += nvram.h header-y += omap3isp.h header-y += omapfb.h +header-y += omap_gpu.h header-y += oom.h header-y += param.h header-y += parport.h diff --git a/include/linux/omap_drm.h b/include/linux/omap_drm.h new file mode 100644 index 00000000000..06b29c55065 --- /dev/null +++ b/include/linux/omap_drm.h @@ -0,0 +1,88 @@ +/* + * linux/include/linux/omap_drm.h + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob@ti.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __OMAP_DRM_H__ +#define __OMAP_DRM_H__ + +#include <linux/module.h> +#include <drm/drmP.h> + +/* interface that plug-in drivers (for now just PVR) can implement */ +struct omap_drm_plugin { + const char *name; + + /* drm functions */ + int (*open)(struct drm_device *dev, struct drm_file *file); + int (*load)(struct drm_device *dev, unsigned long flags); + int (*unload)(struct drm_device *dev); + int (*release)(struct drm_device *dev, struct drm_file *file); + + /* file-ops */ + int (*mmap)(struct file *file, struct vm_area_struct *vma); + + struct drm_ioctl_desc *ioctls; + int num_ioctls; + int ioctl_start; + + struct list_head list; /* note, this means struct can't be const.. */ +}; + +int omap_drm_register_plugin(struct omap_drm_plugin *plugin); +int omap_drm_unregister_plugin(struct omap_drm_plugin *plugin); +struct drm_framebuffer * omap_drm_get_default_fb(struct drm_device *dev); + +enum omap_dss_update_mode omap_connector_get_update_mode( + struct drm_connector *connector); +int omap_connector_set_update_mode(struct drm_connector *connector, + enum omap_dss_update_mode mode); +int omap_connector_sync(struct drm_connector *connector); + +int omap_encoder_wait_for_vsync(struct drm_encoder *encoder); + +int omap_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event); + +struct drm_framebuffer * omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd *mode_cmd); +int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, + void **vaddr, unsigned long *paddr, int *screen_width); +struct drm_connector * omap_framebuffer_get_next_connector( + struct drm_framebuffer *fb, struct drm_connector *from); +void omap_framebuffer_flush(struct drm_framebuffer *fb, + int x, int y, int w, int h); + + +/* optional platform data to configure the default configuration of which + * pipes/overlays/CRTCs are used.. if this is not provided, then instead the + * first CONFIG_DRM_OMAP_NUM_CRTCS are used, and they are each connected to + * one manager, with priority given to managers that are connected to + * detected devices. This should be a good default behavior for most cases, + * but yet there still might be times when you wish to do something different. + */ +struct omap_drm_platform_data { + int ovl_cnt; + const int *ovl_ids; + int mgr_cnt; + const int *mgr_ids; + int dev_cnt; + const char **dev_names; +}; + +#endif /* __OMAP_DRM_H__ */ |