Message ID | 1318607150-19492-1-git-send-email-rob.clark@linaro.org |
---|---|
State | New |
Headers | show |
On Fri, Oct 14, 2011 at 10:45:50AM -0500, Rob Clark wrote: > From: Rob Clark <rob@ti.com> > > A DRM display driver for TI OMAP platform. Similar to omapfb (fbdev) > and omap_vout (v4l2 display) drivers in the past, this driver uses the > DSS2 driver to access the display hardware, including support for > HDMI, DVI, and various types of LCD panels. And it implements GEM > support for buffer allocation (for KMS as well as offscreen buffers > used by the xf86-video-omap userspace xorg driver). > > The driver maps CRTCs to overlays, encoders to overlay-managers, and > connectors to dssdev's. Note that this arrangement might change slightly > when support for drm_plane overlays is added. > > For GEM support, non-scanout buffers are using the shmem backed pages > provided by GEM core (In drm_gem_object_init()). In the case of scanout > buffers, which need to be physically contiguous, those are allocated > with CMA and use drm_gem_private_object_init(). > > See userspace xorg driver: > git://github.com/robclark/xf86-video-omap.git > > Refer to this link for CMA (Continuous Memory Allocator): > http://lkml.org/lkml/2011/8/19/302 > > Links to previous versions of the patch: > v1: http://lwn.net/Articles/458137/ > v2: http://lwn.net/Articles/459397/ > > History: > > v3: minor cleanups, improved error handling for dev_load(), some minor > API changes that will be needed later for tiled buffer support > v2: replace omap_vram with CMA for scanout buffer allocation, remove > unneeded functions, use dma_addr_t for physical addresses, error > handling cleanup, refactor attach/detach pages into common drm > functions, split non-userspace-facing API into omap_priv.h, remove > plugin API > > v1: original > > Signed-off-by: Rob Clark <rob@ti.com> I've gone through the code once more and found a few more things to add to your TODO. Also, you haven't added the two general points from the last review that I think need to be checked before moving out of staging: - reviewing kms/dss interface mismatches (I've got a few comments about this inline). - reviewing the sync object handling/infrastructure. With these and the stuff below added to the staging TODO: Reviewed-by: Daniel Vetter <daniel.vetter@ffwll.ch> (for -staging). > --- > drivers/staging/Kconfig | 2 + > drivers/staging/Makefile | 1 + > drivers/staging/omapdrm/Kconfig | 24 + > drivers/staging/omapdrm/Makefile | 9 + > drivers/staging/omapdrm/TODO.txt | 22 + > drivers/staging/omapdrm/omap_connector.c | 360 ++++++++++++++ > drivers/staging/omapdrm/omap_crtc.c | 327 +++++++++++++ > drivers/staging/omapdrm/omap_drv.c | 777 ++++++++++++++++++++++++++++++ > drivers/staging/omapdrm/omap_drv.h | 125 +++++ > drivers/staging/omapdrm/omap_encoder.c | 188 +++++++ > drivers/staging/omapdrm/omap_fb.c | 261 ++++++++++ > drivers/staging/omapdrm/omap_fbdev.c | 304 ++++++++++++ > drivers/staging/omapdrm/omap_gem.c | 769 +++++++++++++++++++++++++++++ > drivers/video/omap2/omapfb/Kconfig | 4 +- > include/drm/Kbuild | 1 + > include/drm/omap_drm.h | 123 +++++ > include/drm/omap_priv.h | 42 ++ > 17 files changed, 3337 insertions(+), 2 deletions(-) > create mode 100644 drivers/staging/omapdrm/Kconfig > create mode 100644 drivers/staging/omapdrm/Makefile > create mode 100644 drivers/staging/omapdrm/TODO.txt > create mode 100644 drivers/staging/omapdrm/omap_connector.c > create mode 100644 drivers/staging/omapdrm/omap_crtc.c > create mode 100644 drivers/staging/omapdrm/omap_drv.c > create mode 100644 drivers/staging/omapdrm/omap_drv.h > create mode 100644 drivers/staging/omapdrm/omap_encoder.c > create mode 100644 drivers/staging/omapdrm/omap_fb.c > create mode 100644 drivers/staging/omapdrm/omap_fbdev.c > create mode 100644 drivers/staging/omapdrm/omap_gem.c > create mode 100644 include/drm/omap_drm.h > create mode 100644 include/drm/omap_priv.h > > diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig > index 2abfa28..11a0c85 100644 > --- a/drivers/staging/Kconfig > +++ b/drivers/staging/Kconfig > @@ -148,4 +148,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 f3c5e33..9a24050 100644 > --- a/drivers/staging/Makefile > +++ b/drivers/staging/Makefile > @@ -66,3 +66,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 0000000..1ec24f2 > --- /dev/null > +++ b/drivers/staging/omapdrm/Kconfig > @@ -0,0 +1,24 @@ > + > +config DRM_OMAP > + tristate "OMAP DRM" > + depends on DRM && !CONFIG_FB_OMAP2 > + select DRM_KMS_HELPER > + select OMAP2_DSS > + select FB_SYS_FILLRECT > + select FB_SYS_COPYAREA > + select FB_SYS_IMAGEBLIT > + select FB_SYS_FOPS > + default n > + 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. Imo better serverd with a module param. But given that it'll hopefully disappear with drm core plane support, I don't care. > + > diff --git a/drivers/staging/omapdrm/Makefile b/drivers/staging/omapdrm/Makefile > new file mode 100644 > index 0000000..d0d6c20 > --- /dev/null > +++ b/drivers/staging/omapdrm/Makefile > @@ -0,0 +1,9 @@ > +# > +# Makefile for the drm device driver. This driver provides support for the > +# Direct Rendering Infrastructure (DRI) > +# > + > +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 omap_gem.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 0000000..2ac374f > --- /dev/null > +++ b/drivers/staging/omapdrm/TODO.txt > @@ -0,0 +1,22 @@ > +TODO > +. check error handling/cleanup paths > +. add drm_plane / overlay support > +. add video decode/encode support (via syslink3 + codec-engine) > +. still some rough edges with flipping.. event back to userspace should > + really come after VSYNC interrupt > +. where should we do eviction (detatch_pages())? We aren't necessarily > + accessing the pages via a GART, so maybe we need some other threshold > + to put a cap on the # of pages that can be pin'd. (It is mostly only > + of interest in case you have a swap partition/file.. which a lot of > + these devices do not.. but it doesn't hurt for the driver to do the > + right thing anyways.) Register a shrinker, see i915 for an example (and the deadlock-avoidance dance). > +. GEM/shmem backed pages can have existing mappings (kernel linear map, > + etc..), which isn't really ideal. > + > +Userspace: > +. git://github.com/robclark/xf86-video-omap.git > + > +Currently tested on > +. OMAP3530 beagleboard > +. OMAP4430 pandaboard > +. OMAP4460 pandaboard > diff --git a/drivers/staging/omapdrm/omap_connector.c b/drivers/staging/omapdrm/omap_connector.c > new file mode 100644 > index 0000000..c595de4 > --- /dev/null > +++ b/drivers/staging/omapdrm/omap_connector.c > @@ -0,0 +1,360 @@ > +/* > + * 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 "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; > + > + DBG("%s: %d", dssdev->name, mode); > + > + if (mode == DRM_MODE_DPMS_ON) { > + /* 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: { > + int ret = dssdev->driver->enable(dssdev); > + if (ret) { > + DBG("%s: failed to enable: %d", > + dssdev->name, ret); > + dssdev->driver->disable(dssdev); > + } > + break; > + } > + default: > + break; > + } > + } else { > + /* TODO */ > + } > +} > + > +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->detect) { > + if (dssdrv->detect(dssdev)) { > + ret = connector_status_connected; > + } else { > + ret = connector_status_disconnected; > + } > + } else { > + ret = connector_status_unknown; > + } > + > + 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); > + struct omap_dss_device *dssdev = omap_connector->dssdev; > + > + dssdev->driver->disable(dssdev); > + > + DBG("%s", omap_connector->dssdev->name); > + drm_sysfs_connector_remove(connector); > + drm_connector_cleanup(connector); > + kfree(omap_connector); > + > + omap_dss_put_device(dssdev); > +} > + > +#define MAX_EDID 512 > + > +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->read_edid) { > + void *edid = kzalloc(MAX_EDID, GFP_KERNEL); > + > + if ((dssdrv->read_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); > +} > + > +/* 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_dss_get_device(dssdev); > + > + 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); > + > + 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 0000000..fd09bcf > --- /dev/null > +++ b/drivers/staging/omapdrm/omap_crtc.c > @@ -0,0 +1,327 @@ > +/* > + * 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 "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; > + > + /* if there is a pending flip, this will be non-null: */ > + struct drm_pending_vblank_event *event; > +}; > + > +/* 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 %08x", info->pos_x, info->pos_y, 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 settings\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); > + dma_addr_t paddr; > + unsigned int screen_width; > + > + omap_framebuffer_get_buffer(crtc->fb, crtc->x, crtc->y, > + NULL, &paddr, &screen_width); > + > + DBG("%s: %d,%d: %08x (%d)", omap_crtc->ovl->name, > + crtc->x, crtc->y, (u32)paddr, screen_width); > + > + omap_crtc->info.paddr = paddr; > + 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); > +} > + > +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; > + } > + > + WARN_ON(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); > + 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 void page_flip_cb(void *arg) > +{ > + struct drm_crtc *crtc = arg; > + struct drm_device *dev = crtc->dev; > + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); > + struct drm_pending_vblank_event *event = omap_crtc->event; > + struct timeval now; > + unsigned long flags; > + > + WARN_ON(!event); > + > + omap_crtc->event = NULL; > + > + update_scanout(crtc); > + WARN_ON(commit(crtc)); > + > + /* wakeup userspace */ > + /* TODO: this should happen *after* flip in vsync IRQ handler */ > + 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); > + } > +} > + > +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); > + > + DBG("%d -> %d", crtc->fb ? crtc->fb->base.id : -1, fb->base.id); > + > + if (omap_crtc->event) { > + dev_err(dev->dev, "already a pending flip\n"); > + return -EINVAL; > + } > + > + crtc->fb = fb; > + omap_crtc->event = event; > + > + omap_gem_op_async(omap_framebuffer_bo(fb), OMAP_GEM_READ, > + page_flip_cb, crtc); > + > + return 0; > +} > + > +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 0000000..2491571 > --- /dev/null > +++ b/drivers/staging/omapdrm/omap_drv.c > @@ -0,0 +1,777 @@ > +/* > + * 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 "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 "20110917" > +#define DRIVER_MAJOR 1 > +#define DRIVER_MINOR 0 > +#define DRIVER_PATCHLEVEL 0 > + > +struct drm_device *drm_device; > + > +/* > + * 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; > + /* fallthrough */ > + 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: %s\n", > + mgr->name); > + return -ENOMEM; > + } > + > + BUG_ON(priv->num_encoders >= ARRAY_SIZE(priv->encoders)); > + > + priv->encoders[priv->num_encoders++] = encoder; > + > + return 0; > + } Woot, I've totally overlooked these nested functions on the previous reviews. I didn't even know that this exists. Just move these out&ahead as static functions. > + /* 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->read_edid)) { > + dev_warn(dev->dev, "%s driver does not support " > + "get_timings or read_edid.. skipping it!\n", > + dssdev->name); > + return 0; > + } > + > + connector = omap_connector_init(dev, > + get_connector_type(dssdev), dssdev); > + > + if (!connector) { > + dev_err(dev->dev, "could not create connector: %s\n", > + dssdev->name); > + 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); > + } > + > + BUG_ON(priv->num_connectors >= ARRAY_SIZE(priv->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: > + * > + * note: it might not be immediately apparent, but thanks to > + * the !mgr check in both this loop and the one above, the only > + * way to enter this loop is with j == priv->num_connectors, > + * so idx can never go negative. > + */ Erhh, why not reinit j instead of this massive comment? Also, with these functions no longer nested, you gain one level of indentation and can easily enclose this in a if (!mgr) block. > + 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: %s\n", > + ovl->name); > + return -ENOMEM; > + } > + > + BUG_ON(priv->num_crtcs >= ARRAY_SIZE(priv->crtcs)); > + > + 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 = 256; > + dev->mode_config.min_height = 256; > + > + /* 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; Aside we usually put the limits of the scanout engine here, not the limits of the 3d core. E.g. i915 has 4k scanout limit with a 2k limit for the 2d core, too (for gen3 chipsets). > + dev->mode_config.funcs = &omap_mode_config_funcs; > + > + return 0; > +} > + > +static void omap_modeset_free(struct drm_device *dev) > +{ > + drm_mode_config_cleanup(dev); > +} > + > +/* > + * drm ioctl funcs > + */ > + > + > +static int ioctl_get_param(struct drm_device *dev, void *data, > + struct drm_file *file_priv) > +{ > + struct drm_omap_param *args = data; > + > + DBG("%p: param=%llu", dev, args->param); > + > + switch (args->param) { > + case OMAP_PARAM_CHIPSET_ID: > + args->value = GET_OMAP_TYPE; > + break; > + default: > + DBG("unknown parameter %lld", args->param); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int ioctl_set_param(struct drm_device *dev, void *data, > + struct drm_file *file_priv) > +{ > + struct drm_omap_param *args = data; > + > + switch (args->param) { > + default: > + DBG("unknown parameter %lld", args->param); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int ioctl_gem_new(struct drm_device *dev, void *data, > + struct drm_file *file_priv) > +{ > + struct drm_omap_gem_new *args = data; > + DBG("%p:%p: size=0x%08x, flags=%08x", dev, file_priv, > + args->size.bytes, args->flags); > + return omap_gem_new_handle(dev, file_priv, args->size, > + args->flags, &args->handle); > +} > + > +static int ioctl_gem_cpu_prep(struct drm_device *dev, void *data, > + struct drm_file *file_priv) > +{ > + struct drm_omap_gem_cpu_prep *args = data; > + struct drm_gem_object *obj; > + int ret; > + > + VERB("%p:%p: handle=%d, op=%x", dev, file_priv, args->handle, args->op); > + > + obj = drm_gem_object_lookup(dev, file_priv, args->handle); > + if (!obj) { > + return -ENOENT; > + } > + > + ret = omap_gem_op_sync(obj, args->op); > + > + if (!ret) { > + ret = omap_gem_op_start(obj, args->op); > + } > + > + drm_gem_object_unreference_unlocked(obj); > + > + return ret; > +} > + > +static int ioctl_gem_cpu_fini(struct drm_device *dev, void *data, > + struct drm_file *file_priv) > +{ > + struct drm_omap_gem_cpu_fini *args = data; > + struct drm_gem_object *obj; > + int ret; > + > + VERB("%p:%p: handle=%d", dev, file_priv, args->handle); > + > + obj = drm_gem_object_lookup(dev, file_priv, args->handle); > + if (!obj) { > + return -ENOENT; > + } > + > + /* XXX flushy, flushy */ > + ret = 0; > + > + if (!ret) { > + ret = omap_gem_op_finish(obj, args->op); > + } > + > + drm_gem_object_unreference_unlocked(obj); > + > + return ret; > +} > + > +static int ioctl_gem_info(struct drm_device *dev, void *data, > + struct drm_file *file_priv) > +{ > + struct drm_omap_gem_info *args = data; > + struct drm_gem_object *obj; > + int ret = 0; > + > + DBG("%p:%p: handle=%d", dev, file_priv, args->handle); > + > + obj = drm_gem_object_lookup(dev, file_priv, args->handle); > + if (!obj) { > + return -ENOENT; > + } > + > + args->size = obj->size; /* for now */ > + args->offset = omap_gem_mmap_offset(obj); > + > + drm_gem_object_unreference_unlocked(obj); > + > + return ret; > +} > + > +struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = { > + DRM_IOCTL_DEF_DRV(OMAP_GET_PARAM, ioctl_get_param, DRM_UNLOCKED|DRM_AUTH), > + DRM_IOCTL_DEF_DRV(OMAP_SET_PARAM, ioctl_set_param, DRM_UNLOCKED|DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), > + DRM_IOCTL_DEF_DRV(OMAP_GEM_NEW, ioctl_gem_new, DRM_UNLOCKED|DRM_AUTH), > + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_PREP, ioctl_gem_cpu_prep, DRM_UNLOCKED|DRM_AUTH), > + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_FINI, ioctl_gem_cpu_fini, DRM_UNLOCKED|DRM_AUTH), > + DRM_IOCTL_DEF_DRV(OMAP_GEM_INFO, ioctl_gem_info, DRM_UNLOCKED|DRM_AUTH), > +}; > + > +/* > + * 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; > + 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 -ENOMEM; > + } > + > + dev->dev_private = priv; > + > + ret = omap_modeset_init(dev); > + if (ret) { > + dev_err(dev->dev, "omap_modeset_init failed: ret=%d\n", ret); > + dev->dev_private = NULL; > + kfree(priv); > + return ret; > + } > + > + priv->fbdev = omap_fbdev_init(dev); > + if (!priv->fbdev) { > + dev_warn(dev->dev, "omap_fbdev_init failed\n"); > + /* well, limp along without an fbdev.. maybe X11 will work? */ > + } > + > + drm_kms_helper_poll_init(dev); > + > + ret = drm_vblank_init(dev, priv->num_crtcs); > + if (ret) { > + dev_warn(dev->dev, "could not init vblank\n"); > + } > + > + return 0; > +} > + > +static int dev_unload(struct drm_device *dev) > +{ > + DBG("unload: dev=%p", dev); > + > + drm_vblank_cleanup(dev); > + drm_kms_helper_poll_fini(dev); > + > + omap_fbdev_free(dev); > + > + omap_modeset_free(dev); > + > + kfree(dev->dev_private); > + dev->dev_private = NULL; > + > + return 0; > +} > + > +static int dev_open(struct drm_device *dev, struct drm_file *file) > +{ > + file->driver_priv = NULL; > + > + DBG("open: dev=%p, file=%p", 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); > +} You don't restore the kernel console here. Also, the above comment about agp probably doesn't apply ;-) > +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) > +{ > + DBG("postclose: dev=%p, file=%p", dev, file); > +} > + > +/** > + * 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); > +} > + > +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 struct vm_operations_struct omap_gem_vm_ops = { > + .fault = omap_gem_fault, > + .open = drm_gem_vm_open, > + .close = drm_gem_vm_close, > +}; > + > +static struct drm_driver omap_drm_driver = { > + .driver_features = > + DRIVER_HAVE_IRQ | DRIVER_MODESET | DRIVER_GEM, > + .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, > + .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, > + .gem_init_object = omap_gem_init_object, > + .gem_free_object = omap_gem_free_object, > + .gem_vm_ops = &omap_gem_vm_ops, > + .dumb_create = omap_gem_dumb_create, > + .dumb_map_offset = omap_gem_dumb_map_offset, > + .dumb_destroy = omap_gem_dumb_destroy, > + .ioctls = ioctls, > + .num_ioctls = DRM_OMAP_NUM_IOCTLS, > + .fops = { > + .owner = THIS_MODULE, > + .open = drm_open, > + .unlocked_ioctl = drm_ioctl, > + .release = drm_release, > + .mmap = omap_gem_mmap, > + .poll = drm_poll, > + .fasync = drm_fasync, > + .read = drm_read, > + .llseek = noop_llseek, > + }, > + .name = DRIVER_NAME, > + .desc = DRIVER_DESC, > + .date = DRIVER_DATE, > + .major = DRIVER_MAJOR, > + .minor = DRIVER_MINOR, > + .patchlevel = DRIVER_PATCHLEVEL, > +}; > + > +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 0000000..dc67713 > --- /dev/null > +++ b/drivers/staging/omapdrm/omap_drv.h > @@ -0,0 +1,125 @@ > +/* > + * 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> > +#include <linux/types.h> > +#include <drm/drmP.h> > +#include <drm/omap_drm.h> > +#include <drm/omap_priv.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" > + > +/* max # of mapper-id's that can be assigned.. todo, come up with a better > + * (but still inexpensive) way to store/access per-buffer mapper private > + * data.. > + */ > +#define MAX_MAPPERS 2 > + > +struct omap_drm_private { > + unsigned int num_crtcs; > + struct drm_crtc *crtcs[8]; > + unsigned int num_encoders; > + struct drm_encoder *encoders[8]; > + unsigned int num_connectors; > + struct drm_connector *connectors[8]; > + > + struct drm_fb_helper *fbdev; > +}; > + > +struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev); > +void omap_fbdev_free(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); > +struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, > + struct drm_mode_fb_cmd *mode_cmd, struct drm_gem_object *bo); > +struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb); > +int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, > + void **vaddr, dma_addr_t *paddr, unsigned 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); > + > + > +struct drm_gem_object *omap_gem_new(struct drm_device *dev, > + union omap_gem_size gsize, uint32_t flags); > +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, > + union omap_gem_size gsize, uint32_t flags, uint32_t *handle); > +void omap_gem_free_object(struct drm_gem_object *obj); > +int omap_gem_init_object(struct drm_gem_object *obj); > +void *omap_gem_vaddr(struct drm_gem_object *obj); > +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, > + uint32_t handle, uint64_t *offset); > +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, > + uint32_t handle); > +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, > + struct drm_mode_create_dumb *args); > +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma); > +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); > +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op); > +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op); > +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op); > +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, > + void (*fxn)(void *arg), void *arg); > +int omap_gem_get_paddr(struct drm_gem_object *obj, > + dma_addr_t *paddr, bool remap); > +int omap_gem_put_paddr(struct drm_gem_object *obj); > +uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj); > + > +static inline int align_pitch(int pitch, int width, int bpp) > +{ > + int bytespp = (bpp + 7) / 8; > + /* in case someone tries to feed us a completely bogus stride: */ > + pitch = max(pitch, width * bytespp); > + /* PVR needs alignment to 8 pixels.. right now that is the most > + * restrictive stride requirement.. > + */ > + return ALIGN(pitch, 8 * bytespp); > +} > + > +#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 0000000..33209cf > --- /dev/null > +++ b/drivers/staging/omapdrm/omap_encoder.c > @@ -0,0 +1,188 @@ > +/* > + * 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 "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); > + } > + } > +} I think the dpms helpers are a bad fit for you, and your abusing infrastructure a bit ;-) I think better would be to but omap_connector_dpms into the connector dpms function and maybe call drm_helper_connector_dpms from there, if you still need the outmagic dpms calls on crtcs (with a dummy dpms function on encoders). Core drm only supports dpms on an connector. The helper function is just for the common case where you only have dpms state on crtcs/encoders and they need to be as active as the most active connector (see the drm_helper_choose_*_dpms functions, too). > +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); > + } > + } This also looks like something the drm helpers should do for you or you're using them in a strange way. drm core does the modeset on a crtc only, drm_crtc_helper_set_config should then do the right thing of walking over all connectors/encoders calling set_mode and finally the mode_set_base on the crtc (roughly). I think you need to recheck what stuff you're setting in connectors and what in encoders (or if they are just dummies with a 1:1 connector mapping). > +} > + > +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; > +} > + > +/* 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 0000000..82ed612 > --- /dev/null > +++ b/drivers/staging/omapdrm/omap_fb.c > @@ -0,0 +1,261 @@ > +/* > + * 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 "omap_drv.h" > + > +#include "drm_crtc.h" > +#include "drm_crtc_helper.h" > + > + > +/* > + * framebuffer funcs > + */ > + > +#define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) > + > +struct omap_framebuffer { > + struct drm_framebuffer base; > + struct drm_gem_object *bo; > + int size; > + dma_addr_t paddr; > +}; > + > +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); > + return drm_gem_handle_create(file_priv, omap_fb->bo, handle); > +} > + > +static void omap_framebuffer_destroy(struct drm_framebuffer *fb) > +{ > + struct drm_device *dev = fb->dev; > + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); > + > + DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); > + > + drm_framebuffer_cleanup(fb); This is a bit a general mess in kms. All drivers need to call this, and for added hilarity - drm_crtc.c drm_mode_rmfb has a TODO that this is missing - nouveau/radeon only do this _after_ unref'ing the backing storage - gma500 also tries to restore the kernel console here which should be done in lastclose (because you might disable another userspace fb on another output). Can I prod you to write the patches to clean this up and move drm_framebuffer_cleanup into common code? > + if (omap_gem_put_paddr(omap_fb->bo)) { > + dev_err(dev->dev, "could not unmap!\n"); > + } > + > + if (omap_fb->bo) { > + drm_gem_object_unreference_unlocked(omap_fb->bo); > + } > + > + 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, > +}; > + > +/* returns the buffer size */ > +int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, > + void **vaddr, dma_addr_t *paddr, unsigned int *screen_width) > +{ > + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); > + int bpp = fb->bits_per_pixel / 8; > + unsigned long offset; > + > + offset = (x * bpp) + (y * fb->pitch); > + > + if (vaddr) { > + void *bo_vaddr = omap_gem_vaddr(omap_fb->bo); > + /* note: we can only count on having a vaddr for buffers that > + * are allocated physically contiguously to begin with (ie. > + * dma_alloc_coherent()). But this should be ok because it > + * is only used by legacy fbdev > + */ > + BUG_ON(!bo_vaddr); > + *vaddr = bo_vaddr + offset; > + } > + > + *paddr = omap_fb->paddr + offset; > + *screen_width = fb->pitch / bpp; > + > + return omap_fb->size - offset; > +} > + > +struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb) > +{ > + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); > + return omap_fb->bo; > +} > + > +/* 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; > +} > + > +/* 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); > + } > + } > +} > + > +struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, > + struct drm_file *file, struct drm_mode_fb_cmd *mode_cmd) > +{ > + struct drm_gem_object *bo; > + struct drm_framebuffer *fb; > + bo = drm_gem_object_lookup(dev, file, mode_cmd->handle); > + if (!bo) { > + return ERR_PTR(-ENOENT); > + } > + fb = omap_framebuffer_init(dev, mode_cmd, bo); > + if (!fb) { > + return ERR_PTR(-ENOMEM); > + } > + return fb; > +} > + > +struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, > + struct drm_mode_fb_cmd *mode_cmd, struct drm_gem_object *bo) > +{ > + struct omap_framebuffer *omap_fb; > + struct drm_framebuffer *fb = NULL; > + int size, ret; > + > + DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%d)", > + dev, mode_cmd, mode_cmd->width, mode_cmd->height, > + mode_cmd->bpp); > + > + /* in case someone tries to feed us a completely bogus stride: */ > + mode_cmd->pitch = align_pitch(mode_cmd->pitch, > + mode_cmd->width, mode_cmd->bpp); > + > + 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); > + > + if (bo) { > + DBG("using existing %d byte buffer (needed %d)", bo->size, size); > + if (size > bo->size) { > + dev_err(dev->dev, "provided buffer object is too small!\n"); > + goto fail; > + } > + } else { > + /* for convenience of all the various callers who don't want > + * to be bothered to allocate their own buffer.. > + */ > + union omap_gem_size gsize = { > + .bytes = size, > + }; > + DBG("allocating %d bytes for fb %d", size, dev->primary->index); > + bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); > + if (!bo) { > + dev_err(dev->dev, "failed to allocate buffer object\n"); > + goto fail; > + } > + } > + > + omap_fb->bo = bo; > + omap_fb->size = size; > + > + if (omap_gem_get_paddr(bo, &omap_fb->paddr, true)) { > + dev_err(dev->dev, "could not map (paddr)!\n"); > + goto fail; > + } > + > + drm_helper_mode_fill_fb_struct(fb, mode_cmd); > + > + return fb; > + > +fail: > + if (fb) { > + omap_framebuffer_destroy(fb); > + } > + return NULL; > +} > diff --git a/drivers/staging/omapdrm/omap_fbdev.c b/drivers/staging/omapdrm/omap_fbdev.c > new file mode 100644 > index 0000000..048077c > --- /dev/null > +++ b/drivers/staging/omapdrm/omap_fbdev.c > @@ -0,0 +1,304 @@ > +/* > + * 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 "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 = NULL; > + struct fb_info *fbi = NULL; > + struct drm_mode_fb_cmd mode_cmd = {0}; > + dma_addr_t 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; > + > + fb = omap_framebuffer_init(dev, &mode_cmd, NULL); > + if (!fb) { > + dev_err(dev->dev, "failed to allocate fb\n"); > + ret = -ENOMEM; > + goto fail; > + } > + > + 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_unlock; > + } > + > + DBG("fbi=%p, dev=%p", fbi, dev); > + > + fbdev->fb = 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_unlock; > + } > + > + 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_unlock: > + mutex_unlock(&dev->struct_mutex); > +fail: > + > + if (ret) { > + if (fbi) > + framebuffer_release(fbi); > + if (fb) > + fb->funcs->destroy(fb); > + } > + > + 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"); > +} > + > +static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc, > + u16 *red, u16 *green, u16 *blue, int regno) > +{ > + DBG("fbdev: get gamma"); > +} > + > +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); > +} > + > +/* 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: > + kfree(fbdev); > + return NULL; > +} > + > +void omap_fbdev_free(struct drm_device *dev) > +{ > + struct omap_drm_private *priv = dev->dev_private; > + struct drm_fb_helper *helper = priv->fbdev; > + struct omap_fbdev *fbdev; > + struct fb_info *fbi; > + > + DBG(); > + > + fbi = helper->fbdev; > + > + unregister_framebuffer(fbi); > + framebuffer_release(fbi); > + > + drm_fb_helper_fini(helper); > + > + fbdev = to_omap_fbdev(priv->fbdev); > + > + kfree(fbdev); > + > + priv->fbdev = NULL; > +} > diff --git a/drivers/staging/omapdrm/omap_gem.c b/drivers/staging/omapdrm/omap_gem.c > new file mode 100644 > index 0000000..3210059 > --- /dev/null > +++ b/drivers/staging/omapdrm/omap_gem.c > @@ -0,0 +1,769 @@ > +/* > + * drivers/staging/omapdrm/omap_gem.c > + * > + * Copyright (C) 2011 Texas Instruments > + * Author: Rob Clark <rob.clark@linaro.org> > + * > + * 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/spinlock.h> > +#include <linux/shmem_fs.h> > + > +#include "omap_drv.h" > + > +/* > + * GEM buffer object implementation. > + */ > + > +#define to_omap_bo(x) container_of(x, struct omap_gem_object, base) > + > +/* note: we use upper 8 bits of flags for driver-internal flags: */ > +#define OMAP_BO_DMA 0x01000000 /* actually is physically contiguous */ > +#define OMAP_BO_EXT_SYNC 0x02000000 /* externally allocated sync object */ > +#define OMAP_BO_EXT_MEM 0x04000000 /* externally allocated memory */ > + > + > +struct omap_gem_object { > + struct drm_gem_object base; > + > + uint32_t flags; > + > + /** > + * If buffer is allocated physically contiguous, the OMAP_BO_DMA flag > + * is set and the paddr is valid. > + * > + * Note that OMAP_BO_SCANOUT is a hint from userspace that DMA capable > + * buffer is requested, but doesn't mean that it is. Use the > + * OMAP_BO_DMA flag to determine if the buffer has a DMA capable > + * physical address. > + */ > + dma_addr_t paddr; > + > + /** > + * Array of backing pages, if allocated. Note that pages are never > + * allocated for buffers originally allocated from contiguous memory > + */ > + struct page **pages; > + > + /** > + * Virtual address, if mapped. > + */ > + void *vaddr; > + > + /** > + * sync-object allocated on demand (if needed) > + * > + * Per-buffer sync-object for tracking pending and completed hw/dma > + * read and write operations. The layout in memory is dictated by > + * the SGX firmware, which uses this information to stall the command > + * stream if a surface is not ready yet. > + * > + * Note that when buffer is used by SGX, the sync-object needs to be > + * allocated from a special heap of sync-objects. This way many sync > + * objects can be packed in a page, and not waste GPU virtual address > + * space. Because of this we have to have a omap_gem_set_sync_object() > + * API to allow replacement of the syncobj after it has (potentially) > + * already been allocated. A bit ugly but I haven't thought of a > + * better alternative. > + */ > + struct { > + uint32_t write_pending; > + uint32_t write_complete; > + uint32_t read_pending; > + uint32_t read_complete; > + } *sync; > +}; > + > +/* GEM objects can either be allocated from contiguous memory (in which > + * case obj->filp==NULL), or w/ shmem backing (obj->filp!=NULL). But non > + * contiguous buffers can be remapped in TILER/DMM if they need to be > + * contiguous... but we don't do this all the time to reduce pressure > + * on TILER/DMM space when we know at allocation time that the buffer > + * will need to be scanned out. > + */ > +static inline bool is_shmem(struct drm_gem_object *obj) > +{ > + return obj->filp != NULL; > +} > + > +static int get_pages(struct drm_gem_object *obj, struct page ***pages); > + > +static DEFINE_SPINLOCK(sync_lock); > + > +/** ensure backing pages are allocated */ > +static int omap_gem_attach_pages(struct drm_gem_object *obj) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + struct page **pages; > + > + WARN_ON(omap_obj->pages); > + > + /* TODO: __GFP_DMA32 .. but somehow GFP_HIGHMEM is coming from the > + * mapping_gfp_mask(mapping) which conflicts w/ GFP_DMA32.. probably > + * we actually want CMA memory for it all anyways.. > + */ > + pages = drm_gem_get_pages(obj, GFP_KERNEL); > + if (IS_ERR(pages)) { > + dev_err(obj->dev->dev, "could not get pages: %ld\n", PTR_ERR(pages)); > + return PTR_ERR(pages); > + } > + > + omap_obj->pages = pages; > + return 0; > +} > + > +/** release backing pages */ > +static void omap_gem_detach_pages(struct drm_gem_object *obj) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + drm_gem_put_pages(obj, omap_obj->pages, true, false); > + omap_obj->pages = NULL; > +} > + > +/** get mmap offset */ > +uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj) > +{ > + if (!obj->map_list.map) { > + /* Make it mmapable */ > + int ret = drm_gem_create_mmap_offset(obj); > + if (ret) { > + dev_err(obj->dev->dev, "could not allocate mmap offset"); > + return 0; > + } > + } > + > + return (uint64_t)obj->map_list.hash.key << PAGE_SHIFT; > +} > + > +/** > + * omap_gem_fault - pagefault handler for GEM objects > + * @vma: the VMA of the GEM object > + * @vmf: fault detail > + * > + * Invoked when a fault occurs on an mmap of a GEM managed area. GEM > + * does most of the work for us including the actual map/unmap calls > + * but we need to do the actual page work. > + * > + * The VMA was set up by GEM. In doing so it also ensured that the > + * vma->vm_private_data points to the GEM object that is backing this > + * mapping. > + */ > +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) > +{ > + struct drm_gem_object *obj = vma->vm_private_data; > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + struct drm_device *dev = obj->dev; > + struct page **pages; > + unsigned long pfn; > + pgoff_t pgoff; > + int ret; > + > + /* Make sure we don't parallel update on a fault, nor move or remove > + * something from beneath our feet > + */ > + mutex_lock(&dev->struct_mutex); > + > + /* if a shmem backed object, make sure we have pages attached now */ > + ret = get_pages(obj, &pages); > + if (ret) { > + goto fail; > + } > + > + /* where should we do corresponding put_pages().. we are mapping > + * the original page, rather than thru a GART, so we can't rely > + * on eviction to trigger this. But munmap() or all mappings should > + * probably trigger put_pages()? > + */ > + > + /* We don't use vmf->pgoff since that has the fake offset: */ > + pgoff = ((unsigned long)vmf->virtual_address - > + vma->vm_start) >> PAGE_SHIFT; > + > + if (omap_obj->pages) { > + pfn = page_to_pfn(omap_obj->pages[pgoff]); > + } else { > + BUG_ON(!(omap_obj->flags & OMAP_BO_DMA)); > + pfn = (omap_obj->paddr >> PAGE_SHIFT) + pgoff; > + } > + > + VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address, > + pfn, pfn << PAGE_SHIFT); > + > + ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn); > + > +fail: > + mutex_unlock(&dev->struct_mutex); > + switch (ret) { > + case 0: > + case -ERESTARTSYS: > + case -EINTR: > + return VM_FAULT_NOPAGE; > + case -ENOMEM: > + return VM_FAULT_OOM; > + default: > + return VM_FAULT_SIGBUS; > + } > +} > + > +/** We override mainly to fix up some of the vm mapping flags.. */ > +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma) > +{ > + struct omap_gem_object *omap_obj; > + int ret; > + > + ret = drm_gem_mmap(filp, vma); > + if (ret) { > + DBG("mmap failed: %d", ret); > + return ret; > + } > + > + /* after drm_gem_mmap(), it is safe to access the obj */ > + omap_obj = to_omap_bo(vma->vm_private_data); > + > + vma->vm_flags &= ~VM_PFNMAP; > + vma->vm_flags |= VM_MIXEDMAP; > + > + if (omap_obj->flags & OMAP_BO_WC) { > + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); > + } else if (omap_obj->flags & OMAP_BO_UNCACHED) { > + vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); > + } else { > + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); > + } > + > + return ret; > +} > + > +/** > + * omap_gem_dumb_create - create a dumb buffer > + * @drm_file: our client file > + * @dev: our device > + * @args: the requested arguments copied from userspace > + * > + * Allocate a buffer suitable for use for a frame buffer of the > + * form described by user space. Give userspace a handle by which > + * to reference it. > + */ > +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, > + struct drm_mode_create_dumb *args) > +{ > + union omap_gem_size gsize; > + > + /* in case someone tries to feed us a completely bogus stride: */ > + args->pitch = align_pitch(args->pitch, args->width, args->bpp); > + args->size = PAGE_ALIGN(args->pitch * args->height); > + > + gsize = (union omap_gem_size){ > + .bytes = args->size, > + }; > + > + return omap_gem_new_handle(dev, file, gsize, > + OMAP_BO_SCANOUT | OMAP_BO_WC, &args->handle); > +} > + > +/** > + * omap_gem_dumb_destroy - destroy a dumb buffer > + * @file: client file > + * @dev: our DRM device > + * @handle: the object handle > + * > + * Destroy a handle that was created via omap_gem_dumb_create. > + */ > +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, > + uint32_t handle) > +{ > + /* No special work needed, drop the reference and see what falls out */ > + return drm_gem_handle_delete(file, handle); > +} > + > +/** > + * omap_gem_dumb_map - buffer mapping for dumb interface > + * @file: our drm client file > + * @dev: drm device > + * @handle: GEM handle to the object (from dumb_create) > + * > + * Do the necessary setup to allow the mapping of the frame buffer > + * into user memory. We don't have to do much here at the moment. > + */ > +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, > + uint32_t handle, uint64_t *offset) > +{ > + struct drm_gem_object *obj; > + int ret = 0; > + > + mutex_lock(&dev->struct_mutex); > + > + /* GEM does all our handle to object mapping */ > + obj = drm_gem_object_lookup(dev, file, handle); > + if (obj == NULL) { > + ret = -ENOENT; > + goto fail; > + } > + > + *offset = omap_gem_mmap_offset(obj); > + > + drm_gem_object_unreference_unlocked(obj); > + > +fail: > + mutex_unlock(&dev->struct_mutex); > + return ret; > +} > + > +/* Get physical address for DMA.. if 'remap' is true, and the buffer is not > + * already contiguous, remap it to pin in physically contiguous memory.. (ie. > + * map in TILER) > + */ > +int omap_gem_get_paddr(struct drm_gem_object *obj, > + dma_addr_t *paddr, bool remap) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + int ret = 0; > + > + if (is_shmem(obj)) { > + /* TODO: remap to TILER */ > + return -ENOMEM; > + } > + > + *paddr = omap_obj->paddr; > + > + return ret; > +} > + > +/* Release physical address, when DMA is no longer being performed.. this > + * could potentially unpin and unmap buffers from TILER > + */ > +int omap_gem_put_paddr(struct drm_gem_object *obj) > +{ > + /* do something here when remap to TILER is used.. */ > + return 0; > +} > + > +/* acquire pages when needed (for example, for DMA where physically > + * contiguous buffer is not required > + */ > +static int get_pages(struct drm_gem_object *obj, struct page ***pages) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + int ret = 0; > + > + if (is_shmem(obj) && !omap_obj->pages) { > + ret = omap_gem_attach_pages(obj); > + if (ret) { > + dev_err(obj->dev->dev, "could not attach pages\n"); > + return ret; > + } > + } > + > + /* TODO: even phys-contig.. we should have a list of pages? */ > + *pages = omap_obj->pages; > + > + return 0; > +} > + > +int omap_gem_get_pages(struct drm_gem_object *obj, struct page ***pages) > +{ > + int ret; > + mutex_lock(&obj->dev->struct_mutex); > + ret = get_pages(obj, pages); > + mutex_unlock(&obj->dev->struct_mutex); > + return ret; > +} > + > +/* release pages when DMA no longer being performed */ > +int omap_gem_put_pages(struct drm_gem_object *obj) > +{ > + /* do something here if we dynamically attach/detach pages.. at > + * least they would no longer need to be pinned if everyone has > + * released the pages.. > + */ > + return 0; > +} > + > +/* Get kernel virtual address for CPU access.. only buffers that are > + * allocated contiguously have a kernel virtual address, so this more > + * or less only exists for omap_fbdev > + */ > +void *omap_gem_vaddr(struct drm_gem_object *obj) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + return omap_obj->vaddr; > +} > + > +/* Buffer Synchronization: > + */ > + > +struct omap_gem_sync_waiter { > + struct list_head list; > + struct omap_gem_object *omap_obj; > + enum omap_gem_op op; > + uint32_t read_target, write_target; > + /* notify called w/ sync_lock held */ > + void (*notify)(void *arg); > + void *arg; > +}; > + > +/* list of omap_gem_sync_waiter.. the notify fxn gets called back when > + * the read and/or write target count is achieved which can call a user > + * callback (ex. to kick 3d and/or 2d), wakeup blocked task (prep for > + * cpu access), etc. > + */ > +static LIST_HEAD(waiters); > + > +static inline bool is_waiting(struct omap_gem_sync_waiter *waiter) > +{ > + struct omap_gem_object *omap_obj = waiter->omap_obj; > + if ((waiter->op & OMAP_GEM_READ) && > + (omap_obj->sync->read_complete < waiter->read_target)) > + return true; > + if ((waiter->op & OMAP_GEM_WRITE) && > + (omap_obj->sync->write_complete < waiter->write_target)) > + return true; > + return false; > +} > + > +/* macro for sync debug.. */ > +#define SYNCDBG 0 > +#define SYNC(fmt, ...) do { if (SYNCDBG) \ > + printk(KERN_ERR "%s:%d: "fmt"\n", \ > + __func__, __LINE__, ##__VA_ARGS__); \ > + } while (0) > + > + > +static void sync_op_update(void) > +{ > + struct omap_gem_sync_waiter *waiter, *n; > + list_for_each_entry_safe(waiter, n, &waiters, list) { > + if (!is_waiting(waiter)) { > + list_del(&waiter->list); > + SYNC("notify: %p", waiter); > + waiter->notify(waiter->arg); > + kfree(waiter); > + } > + } > +} > + > +static inline int sync_op(struct drm_gem_object *obj, > + enum omap_gem_op op, bool start) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + int ret = 0; > + > + spin_lock(&sync_lock); > + > + if (!omap_obj->sync) { > + omap_obj->sync = kzalloc(sizeof(*omap_obj->sync), GFP_ATOMIC); > + if (!omap_obj->sync) { > + ret = -ENOMEM; > + goto unlock; > + } > + } > + > + if (start) { > + if (op & OMAP_GEM_READ) > + omap_obj->sync->read_pending++; > + if (op & OMAP_GEM_WRITE) > + omap_obj->sync->write_pending++; > + } else { > + if (op & OMAP_GEM_READ) > + omap_obj->sync->read_complete++; > + if (op & OMAP_GEM_WRITE) > + omap_obj->sync->write_complete++; > + sync_op_update(); > + } > + > +unlock: > + spin_unlock(&sync_lock); > + > + return ret; > +} > + > +/* it is a bit lame to handle updates in this sort of polling way, but > + * in case of PVR, the GPU can directly update read/write complete > + * values, and not really tell us which ones it updated.. this also > + * means that sync_lock is not quite sufficient. So we'll need to > + * do something a bit better when it comes time to add support for > + * separate 2d hw.. > + */ > +void omap_gem_op_update(void) > +{ > + spin_lock(&sync_lock); > + sync_op_update(); > + spin_unlock(&sync_lock); > +} > + > +/* mark the start of read and/or write operation */ > +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op) > +{ > + return sync_op(obj, op, true); > +} > + > +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op) > +{ > + return sync_op(obj, op, false); > +} > + > +static DECLARE_WAIT_QUEUE_HEAD(sync_event); > + > +static void sync_notify(void *arg) > +{ > + struct task_struct **waiter_task = arg; > + *waiter_task = NULL; > + wake_up_all(&sync_event); > +} > + > +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + int ret = 0; > + if (omap_obj->sync) { > + struct task_struct *waiter_task = current; > + struct omap_gem_sync_waiter *waiter = > + kzalloc(sizeof(*waiter), GFP_KERNEL); > + > + if (!waiter) { > + return -ENOMEM; > + } > + > + waiter->omap_obj = omap_obj; > + waiter->op = op; > + waiter->read_target = omap_obj->sync->read_pending; > + waiter->write_target = omap_obj->sync->write_pending; > + waiter->notify = sync_notify; > + waiter->arg = &waiter_task; > + > + spin_lock(&sync_lock); > + if (is_waiting(waiter)) { > + SYNC("waited: %p", waiter); > + list_add_tail(&waiter->list, &waiters); > + spin_unlock(&sync_lock); > + ret = wait_event_interruptible(sync_event, > + (waiter_task == NULL)); > + spin_lock(&sync_lock); > + if (waiter_task) { > + SYNC("interrupted: %p", waiter); > + /* we were interrupted */ > + list_del(&waiter->list); > + waiter_task = NULL; > + } else { > + /* freed in sync_op_update() */ > + waiter = NULL; > + } > + } > + spin_unlock(&sync_lock); > + > + if (waiter) { > + kfree(waiter); > + } > + } > + return ret; > +} > + > +/* call fxn(arg), either synchronously or asynchronously if the op > + * is currently blocked.. fxn() can be called from any context > + * > + * (TODO for now fxn is called back from whichever context calls > + * omap_gem_op_update().. but this could be better defined later > + * if needed) > + * > + * TODO more code in common w/ _sync().. > + */ > +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, > + void (*fxn)(void *arg), void *arg) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + if (omap_obj->sync) { > + struct omap_gem_sync_waiter *waiter = > + kzalloc(sizeof(*waiter), GFP_ATOMIC); > + > + if (!waiter) { > + return -ENOMEM; > + } > + > + waiter->omap_obj = omap_obj; > + waiter->op = op; > + waiter->read_target = omap_obj->sync->read_pending; > + waiter->write_target = omap_obj->sync->write_pending; > + waiter->notify = fxn; > + waiter->arg = arg; > + > + spin_lock(&sync_lock); > + if (is_waiting(waiter)) { > + SYNC("waited: %p", waiter); > + list_add_tail(&waiter->list, &waiters); > + spin_unlock(&sync_lock); > + return 0; > + } > + > + spin_unlock(&sync_lock); > + } > + > + /* no waiting.. */ > + fxn(arg); > + > + return 0; > +} > + > +/* special API so PVR can update the buffer to use a sync-object allocated > + * from it's sync-obj heap. Only used for a newly allocated (from PVR's > + * perspective) sync-object, so we overwrite the new syncobj w/ values > + * from the already allocated syncobj (if there is one) > + */ > +int omap_gem_set_sync_object(struct drm_gem_object *obj, void *syncobj) > +{ > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + int ret = 0; > + > + spin_lock(&sync_lock); > + > + if ((omap_obj->flags & OMAP_BO_EXT_SYNC) && !syncobj) { > + /* clearing a previously set syncobj */ > + syncobj = kzalloc(sizeof(*omap_obj->sync), GFP_ATOMIC); > + if (!syncobj) { > + ret = -ENOMEM; > + goto unlock; > + } > + memcpy(syncobj, omap_obj->sync, sizeof(*omap_obj->sync)); > + omap_obj->flags &= ~OMAP_BO_EXT_SYNC; > + omap_obj->sync = syncobj; > + } else if (syncobj && !(omap_obj->flags & OMAP_BO_EXT_SYNC)) { > + /* replacing an existing syncobj */ > + if (omap_obj->sync) { > + memcpy(syncobj, omap_obj->sync, sizeof(*omap_obj->sync)); > + kfree(omap_obj->sync); > + } > + omap_obj->flags |= OMAP_BO_EXT_SYNC; > + omap_obj->sync = syncobj; > + } > + > +unlock: > + spin_unlock(&sync_lock); > + return ret; > +} > + > +int omap_gem_init_object(struct drm_gem_object *obj) > +{ > + return -EINVAL; /* unused */ > +} > + > +/* don't call directly.. called from GEM core when it is time to actually > + * free the object.. > + */ > +void omap_gem_free_object(struct drm_gem_object *obj) > +{ > + struct drm_device *dev = obj->dev; > + struct omap_gem_object *omap_obj = to_omap_bo(obj); > + > + if (obj->map_list.map) { > + drm_gem_free_mmap_offset(obj); > + } > + > + /* don't free externally allocated backing memory */ > + if (!(omap_obj->flags & OMAP_BO_EXT_MEM)) { > + if (omap_obj->pages) { > + omap_gem_detach_pages(obj); > + } > + if (!is_shmem(obj)) { > + dma_free_writecombine(dev->dev, obj->size, > + omap_obj->vaddr, omap_obj->paddr); > + } > + } > + > + /* don't free externally allocated syncobj */ > + if (!(omap_obj->flags & OMAP_BO_EXT_SYNC)) { > + kfree(omap_obj->sync); > + } > + > + drm_gem_object_release(obj); > + > + kfree(obj); > +} > + > +/* convenience method to construct a GEM buffer object, and userspace handle */ > +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, > + union omap_gem_size gsize, uint32_t flags, uint32_t *handle) > +{ > + struct drm_gem_object *obj; > + int ret; > + > + obj = omap_gem_new(dev, gsize, flags); > + if (!obj) > + return -ENOMEM; > + > + ret = drm_gem_handle_create(file, obj, handle); > + if (ret) { > + drm_gem_object_release(obj); > + kfree(obj); /* TODO isn't there a dtor to call? just copying i915 */ > + return ret; > + } > + > + /* drop reference from allocate - handle holds it now */ > + drm_gem_object_unreference_unlocked(obj); > + > + return 0; > +} > + > +/* GEM buffer object constructor */ > +struct drm_gem_object *omap_gem_new(struct drm_device *dev, > + union omap_gem_size gsize, uint32_t flags) > +{ > + struct omap_gem_object *omap_obj; > + struct drm_gem_object *obj = NULL; > + size_t size; > + int ret; > + > + if (flags & OMAP_BO_TILED) { > + /* TODO: not implemented yet */ > + goto fail; > + } > + > + size = PAGE_ALIGN(gsize.bytes); > + > + omap_obj = kzalloc(sizeof(*omap_obj), GFP_KERNEL); > + if (!omap_obj) { > + dev_err(dev->dev, "could not allocate GEM object\n"); > + goto fail; > + } > + > + obj = &omap_obj->base; > + > + if (flags & OMAP_BO_SCANOUT) { > + /* attempt to allocate contiguous memory */ > + omap_obj->vaddr = dma_alloc_writecombine(dev->dev, size, > + &omap_obj->paddr, GFP_KERNEL); > + if (omap_obj->vaddr) { > + flags |= OMAP_BO_DMA; > + } > + } > + > + omap_obj->flags = flags; > + > + if (flags & (OMAP_BO_DMA|OMAP_BO_EXT_MEM)) { > + ret = drm_gem_private_object_init(dev, obj, size); > + } else { > + ret = drm_gem_object_init(dev, obj, size); > + } > + > + if (ret) { > + goto fail; > + } > + > + return obj; > + > +fail: > + if (obj) { > + omap_gem_free_object(obj); > + } > + return NULL; > +} > diff --git a/drivers/video/omap2/omapfb/Kconfig b/drivers/video/omap2/omapfb/Kconfig > index aa33386..4ea17dc 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 > + tristate "OMAP2+ frame buffer support" > + depends on FB && OMAP2_DSS && !DRM_OMAP > > select OMAP2_VRAM > select OMAP2_VRFB if ARCH_OMAP2 || ARCH_OMAP3 > diff --git a/include/drm/Kbuild b/include/drm/Kbuild > index 3a60ac8..78d451c 100644 > --- a/include/drm/Kbuild > +++ b/include/drm/Kbuild > @@ -11,3 +11,4 @@ header-y += savage_drm.h > header-y += sis_drm.h > header-y += via_drm.h > header-y += vmwgfx_drm.h > +header-y += omap_drm.h > diff --git a/include/drm/omap_drm.h b/include/drm/omap_drm.h > new file mode 100644 > index 0000000..40167dd > --- /dev/null > +++ b/include/drm/omap_drm.h > @@ -0,0 +1,123 @@ > +/* > + * include/drm/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 "drm.h" > + > +/* Please note that modifications to all structs defined here are > + * subject to backwards-compatibility constraints. > + */ > + > +#define OMAP_PARAM_CHIPSET_ID 1 /* ie. 0x3430, 0x4430, etc */ > + > +struct drm_omap_param { > + uint64_t param; /* in */ > + uint64_t value; /* in (set_param), out (get_param) */ > +}; > + > +#define OMAP_BO_SCANOUT 0x00000001 /* scanout capable (phys contiguous) */ > +#define OMAP_BO_CACHE_MASK 0x00000006 /* cache type mask, see cache modes */ > +#define OMAP_BO_TILED_MASK 0x00000f00 /* tiled mapping mask, see tiled modes */ > + > +/* cache modes */ > +#define OMAP_BO_CACHED 0x00000000 /* default */ > +#define OMAP_BO_WC 0x00000002 /* write-combine */ > +#define OMAP_BO_UNCACHED 0x00000004 /* strongly-ordered (uncached) */ > + > +/* tiled modes */ > +#define OMAP_BO_TILED_8 0x00000100 > +#define OMAP_BO_TILED_16 0x00000200 > +#define OMAP_BO_TILED_32 0x00000300 > +#define OMAP_BO_TILED (OMAP_BO_TILED_8 | OMAP_BO_TILED_16 | OMAP_BO_TILED_32) > + > +union omap_gem_size { > + uint32_t bytes; /* (for non-tiled formats) */ > + struct { > + uint16_t width; > + uint16_t height; > + } tiled; /* (for tiled formats) */ > +}; > + > +struct drm_omap_gem_new { > + union omap_gem_size size; /* in */ > + uint32_t flags; /* in */ > + uint32_t handle; /* out */ > + uint32_t __pad; > +}; > + > +/* mask of operations: */ > +enum omap_gem_op { > + OMAP_GEM_READ = 0x01, > + OMAP_GEM_WRITE = 0x02, > +}; > + > +struct drm_omap_gem_cpu_prep { > + uint32_t handle; /* buffer handle (in) */ > + uint32_t op; /* mask of omap_gem_op (in) */ > +}; > + > +struct drm_omap_gem_cpu_fini { > + uint32_t handle; /* buffer handle (in) */ > + uint32_t op; /* mask of omap_gem_op (in) */ > + /* TODO maybe here we pass down info about what regions are touched > + * by sw so we can be clever about cache ops? For now a placeholder, > + * set to zero and we just do full buffer flush.. > + */ > + uint32_t nregions; > + uint32_t __pad; > +}; > + > +struct drm_omap_gem_info { > + uint32_t handle; /* buffer handle (in) */ > + uint32_t pad; > + uint64_t offset; /* mmap offset (out) */ > + /* note: in case of tiled buffers, the user virtual size can be > + * different from the physical size (ie. how many pages are needed > + * to back the object) which is returned in DRM_IOCTL_GEM_OPEN.. > + * This size here is the one that should be used if you want to > + * mmap() the buffer: > + */ > + uint32_t size; /* virtual size for mmap'ing (out) */ > + uint32_t __pad; > +}; > + > +#define DRM_OMAP_GET_PARAM 0x00 > +#define DRM_OMAP_SET_PARAM 0x01 > +/* placeholder for plugin-api > +#define DRM_OMAP_GET_BASE 0x02 > +*/ > +#define DRM_OMAP_GEM_NEW 0x03 > +#define DRM_OMAP_GEM_CPU_PREP 0x04 > +#define DRM_OMAP_GEM_CPU_FINI 0x05 > +#define DRM_OMAP_GEM_INFO 0x06 > +#define DRM_OMAP_NUM_IOCTLS 0x07 > + > +#define DRM_IOCTL_OMAP_GET_PARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GET_PARAM, struct drm_omap_param) > +#define DRM_IOCTL_OMAP_SET_PARAM DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_SET_PARAM, struct drm_omap_param) > +/* placeholder for plugin-api > +#define DRM_IOCTL_OMAP_GET_BASE DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GET_BASE, struct drm_omap_get_base) > +*/ > +#define DRM_IOCTL_OMAP_GEM_NEW DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GEM_NEW, struct drm_omap_gem_new) > +#define DRM_IOCTL_OMAP_GEM_CPU_PREP DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_GEM_CPU_PREP, struct drm_omap_gem_cpu_prep) > +#define DRM_IOCTL_OMAP_GEM_CPU_FINI DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_GEM_CPU_FINI, struct drm_omap_gem_cpu_fini) > +#define DRM_IOCTL_OMAP_GEM_INFO DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GEM_INFO, struct drm_omap_gem_info) > + > +#endif /* __OMAP_DRM_H__ */ > diff --git a/include/drm/omap_priv.h b/include/drm/omap_priv.h > new file mode 100644 > index 0000000..f482d1e > --- /dev/null > +++ b/include/drm/omap_priv.h > @@ -0,0 +1,42 @@ > +/* > + * include/drm/omap_priv.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_PRIV_H__ > +#define __OMAP_PRIV_H__ > + > +/* Non-userspace facing APIs > + */ > + > +/* 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__ */ > -- > 1.7.5.4 > > _______________________________________________ > dri-devel mailing list > dri-devel@lists.freedesktop.org > http://lists.freedesktop.org/mailman/listinfo/dri-devel
thx Daniel.. I'll shortly be sending an updated patch with some changes based on your comments and some TODO updates.. On Wed, Oct 19, 2011 at 8:27 AM, Daniel Vetter <daniel@ffwll.ch> wrote: > On Fri, Oct 14, 2011 at 10:45:50AM -0500, Rob Clark wrote: [snip] >> + Â Â dev->mode_config.min_width = 256; >> + Â Â dev->mode_config.min_height = 256; >> + >> + Â Â /* 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; > > Aside we usually put the limits of the scanout engine here, not the limits > of the 3d core. E.g. i915 has 4k scanout limit with a 2k limit for the 2d > core, too (for gen3 chipsets). ok, well 2k is currently also the scanout limit, although in future I'll have to add some omap revision # checks.. [snip] >> +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); >> + Â Â Â Â Â Â } >> + Â Â } >> +} > > I think the dpms helpers are a bad fit for you, and your abusing > infrastructure a bit ;-) I think better would be to but > omap_connector_dpms into the connector dpms function and maybe call > drm_helper_connector_dpms from there, if you still need the outmagic dpms > calls on crtcs (with a dummy dpms function on encoders). > > Core drm only supports dpms on an connector. The helper function is just > for the common case where you only have dpms state on crtcs/encoders and > they need to be as active as the most active connector (see the > drm_helper_choose_*_dpms functions, too). ok, I've re-worked this one.. >> +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); >> + Â Â Â Â Â Â } >> + Â Â } > > This also looks like something the drm helpers should do for you or you're > using them in a strange way. > > drm core does the modeset on a crtc only, drm_crtc_helper_set_config > should then do the right thing of walking over all connectors/encoders > calling set_mode and finally the mode_set_base on the crtc (roughly). > > I think you need to recheck what stuff you're setting in connectors and > what in encoders (or if they are just dummies with a 1:1 connector > mapping). ... but this one, I don't see a better way. The problem is that omap_dss_driver is sort of a combination of encoder and connector. So I think this one I live with for now. Long term, once we have drm_plane stuff, I'm not really sure if it is needed to keep separate dss and omapdrm layers. So until then I wasn't really trying to do too much changing of dss APIs. [snip] >> +static void omap_framebuffer_destroy(struct drm_framebuffer *fb) >> +{ >> + Â Â struct drm_device *dev = fb->dev; >> + Â Â struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); >> + >> + Â Â DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); >> + >> + Â Â drm_framebuffer_cleanup(fb); > > This is a bit a general mess in kms. All drivers need to call this, and > for added hilarity > - drm_crtc.c drm_mode_rmfb has a TODO that this is missing > - nouveau/radeon only do this _after_ unref'ing the backing storage > - gma500 also tries to restore the kernel console here which should be > Â done in lastclose (because you might disable another userspace fb on > Â another output). > > Can I prod you to write the patches to clean this up and move > drm_framebuffer_cleanup into common code? Ok, sure.. I'll do this, but as a later patch BR, -R
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 2abfa28..11a0c85 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -148,4 +148,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 f3c5e33..9a24050 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -66,3 +66,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 0000000..1ec24f2 --- /dev/null +++ b/drivers/staging/omapdrm/Kconfig @@ -0,0 +1,24 @@ + +config DRM_OMAP + tristate "OMAP DRM" + depends on DRM && !CONFIG_FB_OMAP2 + select DRM_KMS_HELPER + select OMAP2_DSS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + default n + 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 0000000..d0d6c20 --- /dev/null +++ b/drivers/staging/omapdrm/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) +# + +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 omap_gem.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 0000000..2ac374f --- /dev/null +++ b/drivers/staging/omapdrm/TODO.txt @@ -0,0 +1,22 @@ +TODO +. check error handling/cleanup paths +. add drm_plane / overlay support +. add video decode/encode support (via syslink3 + codec-engine) +. still some rough edges with flipping.. event back to userspace should + really come after VSYNC interrupt +. where should we do eviction (detatch_pages())? We aren't necessarily + accessing the pages via a GART, so maybe we need some other threshold + to put a cap on the # of pages that can be pin'd. (It is mostly only + of interest in case you have a swap partition/file.. which a lot of + these devices do not.. but it doesn't hurt for the driver to do the + right thing anyways.) +. GEM/shmem backed pages can have existing mappings (kernel linear map, + etc..), which isn't really ideal. + +Userspace: +. git://github.com/robclark/xf86-video-omap.git + +Currently tested on +. OMAP3530 beagleboard +. OMAP4430 pandaboard +. OMAP4460 pandaboard diff --git a/drivers/staging/omapdrm/omap_connector.c b/drivers/staging/omapdrm/omap_connector.c new file mode 100644 index 0000000..c595de4 --- /dev/null +++ b/drivers/staging/omapdrm/omap_connector.c @@ -0,0 +1,360 @@ +/* + * 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 "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; + + DBG("%s: %d", dssdev->name, mode); + + if (mode == DRM_MODE_DPMS_ON) { + /* 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: { + int ret = dssdev->driver->enable(dssdev); + if (ret) { + DBG("%s: failed to enable: %d", + dssdev->name, ret); + dssdev->driver->disable(dssdev); + } + break; + } + default: + break; + } + } else { + /* TODO */ + } +} + +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->detect) { + if (dssdrv->detect(dssdev)) { + ret = connector_status_connected; + } else { + ret = connector_status_disconnected; + } + } else { + ret = connector_status_unknown; + } + + 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); + struct omap_dss_device *dssdev = omap_connector->dssdev; + + dssdev->driver->disable(dssdev); + + DBG("%s", omap_connector->dssdev->name); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(omap_connector); + + omap_dss_put_device(dssdev); +} + +#define MAX_EDID 512 + +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->read_edid) { + void *edid = kzalloc(MAX_EDID, GFP_KERNEL); + + if ((dssdrv->read_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); +} + +/* 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_dss_get_device(dssdev); + + 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); + + 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 0000000..fd09bcf --- /dev/null +++ b/drivers/staging/omapdrm/omap_crtc.c @@ -0,0 +1,327 @@ +/* + * 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 "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; + + /* if there is a pending flip, this will be non-null: */ + struct drm_pending_vblank_event *event; +}; + +/* 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 %08x", info->pos_x, info->pos_y, 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 settings\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); + dma_addr_t paddr; + unsigned int screen_width; + + omap_framebuffer_get_buffer(crtc->fb, crtc->x, crtc->y, + NULL, &paddr, &screen_width); + + DBG("%s: %d,%d: %08x (%d)", omap_crtc->ovl->name, + crtc->x, crtc->y, (u32)paddr, screen_width); + + omap_crtc->info.paddr = paddr; + 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); +} + +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; + } + + WARN_ON(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); + 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 void page_flip_cb(void *arg) +{ + struct drm_crtc *crtc = arg; + struct drm_device *dev = crtc->dev; + struct omap_crtc *omap_crtc = to_omap_crtc(crtc); + struct drm_pending_vblank_event *event = omap_crtc->event; + struct timeval now; + unsigned long flags; + + WARN_ON(!event); + + omap_crtc->event = NULL; + + update_scanout(crtc); + WARN_ON(commit(crtc)); + + /* wakeup userspace */ + /* TODO: this should happen *after* flip in vsync IRQ handler */ + 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); + } +} + +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); + + DBG("%d -> %d", crtc->fb ? crtc->fb->base.id : -1, fb->base.id); + + if (omap_crtc->event) { + dev_err(dev->dev, "already a pending flip\n"); + return -EINVAL; + } + + crtc->fb = fb; + omap_crtc->event = event; + + omap_gem_op_async(omap_framebuffer_bo(fb), OMAP_GEM_READ, + page_flip_cb, crtc); + + return 0; +} + +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 0000000..2491571 --- /dev/null +++ b/drivers/staging/omapdrm/omap_drv.c @@ -0,0 +1,777 @@ +/* + * 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 "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 "20110917" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +struct drm_device *drm_device; + +/* + * 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; + /* fallthrough */ + 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: %s\n", + mgr->name); + return -ENOMEM; + } + + BUG_ON(priv->num_encoders >= ARRAY_SIZE(priv->encoders)); + + 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->read_edid)) { + dev_warn(dev->dev, "%s driver does not support " + "get_timings or read_edid.. skipping it!\n", + dssdev->name); + return 0; + } + + connector = omap_connector_init(dev, + get_connector_type(dssdev), dssdev); + + if (!connector) { + dev_err(dev->dev, "could not create connector: %s\n", + dssdev->name); + 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); + } + + BUG_ON(priv->num_connectors >= ARRAY_SIZE(priv->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: + * + * note: it might not be immediately apparent, but thanks to + * the !mgr check in both this loop and the one above, the only + * way to enter this loop is with j == priv->num_connectors, + * so idx can never go negative. + */ + 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: %s\n", + ovl->name); + return -ENOMEM; + } + + BUG_ON(priv->num_crtcs >= ARRAY_SIZE(priv->crtcs)); + + 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 = 256; + dev->mode_config.min_height = 256; + + /* 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; +} + +static void omap_modeset_free(struct drm_device *dev) +{ + drm_mode_config_cleanup(dev); +} + +/* + * drm ioctl funcs + */ + + +static int ioctl_get_param(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_param *args = data; + + DBG("%p: param=%llu", dev, args->param); + + switch (args->param) { + case OMAP_PARAM_CHIPSET_ID: + args->value = GET_OMAP_TYPE; + break; + default: + DBG("unknown parameter %lld", args->param); + return -EINVAL; + } + + return 0; +} + +static int ioctl_set_param(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_param *args = data; + + switch (args->param) { + default: + DBG("unknown parameter %lld", args->param); + return -EINVAL; + } + + return 0; +} + +static int ioctl_gem_new(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_new *args = data; + DBG("%p:%p: size=0x%08x, flags=%08x", dev, file_priv, + args->size.bytes, args->flags); + return omap_gem_new_handle(dev, file_priv, args->size, + args->flags, &args->handle); +} + +static int ioctl_gem_cpu_prep(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_cpu_prep *args = data; + struct drm_gem_object *obj; + int ret; + + VERB("%p:%p: handle=%d, op=%x", dev, file_priv, args->handle, args->op); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + return -ENOENT; + } + + ret = omap_gem_op_sync(obj, args->op); + + if (!ret) { + ret = omap_gem_op_start(obj, args->op); + } + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int ioctl_gem_cpu_fini(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_cpu_fini *args = data; + struct drm_gem_object *obj; + int ret; + + VERB("%p:%p: handle=%d", dev, file_priv, args->handle); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + return -ENOENT; + } + + /* XXX flushy, flushy */ + ret = 0; + + if (!ret) { + ret = omap_gem_op_finish(obj, args->op); + } + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +static int ioctl_gem_info(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_omap_gem_info *args = data; + struct drm_gem_object *obj; + int ret = 0; + + DBG("%p:%p: handle=%d", dev, file_priv, args->handle); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + return -ENOENT; + } + + args->size = obj->size; /* for now */ + args->offset = omap_gem_mmap_offset(obj); + + drm_gem_object_unreference_unlocked(obj); + + return ret; +} + +struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = { + DRM_IOCTL_DEF_DRV(OMAP_GET_PARAM, ioctl_get_param, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_SET_PARAM, ioctl_set_param, DRM_UNLOCKED|DRM_AUTH|DRM_MASTER|DRM_ROOT_ONLY), + DRM_IOCTL_DEF_DRV(OMAP_GEM_NEW, ioctl_gem_new, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_PREP, ioctl_gem_cpu_prep, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_CPU_FINI, ioctl_gem_cpu_fini, DRM_UNLOCKED|DRM_AUTH), + DRM_IOCTL_DEF_DRV(OMAP_GEM_INFO, ioctl_gem_info, DRM_UNLOCKED|DRM_AUTH), +}; + +/* + * 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; + 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 -ENOMEM; + } + + dev->dev_private = priv; + + ret = omap_modeset_init(dev); + if (ret) { + dev_err(dev->dev, "omap_modeset_init failed: ret=%d\n", ret); + dev->dev_private = NULL; + kfree(priv); + return ret; + } + + priv->fbdev = omap_fbdev_init(dev); + if (!priv->fbdev) { + dev_warn(dev->dev, "omap_fbdev_init failed\n"); + /* well, limp along without an fbdev.. maybe X11 will work? */ + } + + drm_kms_helper_poll_init(dev); + + ret = drm_vblank_init(dev, priv->num_crtcs); + if (ret) { + dev_warn(dev->dev, "could not init vblank\n"); + } + + return 0; +} + +static int dev_unload(struct drm_device *dev) +{ + DBG("unload: dev=%p", dev); + + drm_vblank_cleanup(dev); + drm_kms_helper_poll_fini(dev); + + omap_fbdev_free(dev); + + omap_modeset_free(dev); + + kfree(dev->dev_private); + dev->dev_private = NULL; + + return 0; +} + +static int dev_open(struct drm_device *dev, struct drm_file *file) +{ + file->driver_priv = NULL; + + DBG("open: dev=%p, file=%p", 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) +{ + DBG("postclose: dev=%p, file=%p", dev, file); +} + +/** + * 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); +} + +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 struct vm_operations_struct omap_gem_vm_ops = { + .fault = omap_gem_fault, + .open = drm_gem_vm_open, + .close = drm_gem_vm_close, +}; + +static struct drm_driver omap_drm_driver = { + .driver_features = + DRIVER_HAVE_IRQ | DRIVER_MODESET | DRIVER_GEM, + .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, + .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, + .gem_init_object = omap_gem_init_object, + .gem_free_object = omap_gem_free_object, + .gem_vm_ops = &omap_gem_vm_ops, + .dumb_create = omap_gem_dumb_create, + .dumb_map_offset = omap_gem_dumb_map_offset, + .dumb_destroy = omap_gem_dumb_destroy, + .ioctls = ioctls, + .num_ioctls = DRM_OMAP_NUM_IOCTLS, + .fops = { + .owner = THIS_MODULE, + .open = drm_open, + .unlocked_ioctl = drm_ioctl, + .release = drm_release, + .mmap = omap_gem_mmap, + .poll = drm_poll, + .fasync = drm_fasync, + .read = drm_read, + .llseek = noop_llseek, + }, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, +}; + +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 0000000..dc67713 --- /dev/null +++ b/drivers/staging/omapdrm/omap_drv.h @@ -0,0 +1,125 @@ +/* + * 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> +#include <linux/types.h> +#include <drm/drmP.h> +#include <drm/omap_drm.h> +#include <drm/omap_priv.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" + +/* max # of mapper-id's that can be assigned.. todo, come up with a better + * (but still inexpensive) way to store/access per-buffer mapper private + * data.. + */ +#define MAX_MAPPERS 2 + +struct omap_drm_private { + unsigned int num_crtcs; + struct drm_crtc *crtcs[8]; + unsigned int num_encoders; + struct drm_encoder *encoders[8]; + unsigned int num_connectors; + struct drm_connector *connectors[8]; + + struct drm_fb_helper *fbdev; +}; + +struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev); +void omap_fbdev_free(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); +struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd *mode_cmd, struct drm_gem_object *bo); +struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb); +int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, + void **vaddr, dma_addr_t *paddr, unsigned 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); + + +struct drm_gem_object *omap_gem_new(struct drm_device *dev, + union omap_gem_size gsize, uint32_t flags); +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, + union omap_gem_size gsize, uint32_t flags, uint32_t *handle); +void omap_gem_free_object(struct drm_gem_object *obj); +int omap_gem_init_object(struct drm_gem_object *obj); +void *omap_gem_vaddr(struct drm_gem_object *obj); +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, + uint32_t handle, uint64_t *offset); +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, + uint32_t handle); +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args); +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma); +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf); +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op); +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, + void (*fxn)(void *arg), void *arg); +int omap_gem_get_paddr(struct drm_gem_object *obj, + dma_addr_t *paddr, bool remap); +int omap_gem_put_paddr(struct drm_gem_object *obj); +uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj); + +static inline int align_pitch(int pitch, int width, int bpp) +{ + int bytespp = (bpp + 7) / 8; + /* in case someone tries to feed us a completely bogus stride: */ + pitch = max(pitch, width * bytespp); + /* PVR needs alignment to 8 pixels.. right now that is the most + * restrictive stride requirement.. + */ + return ALIGN(pitch, 8 * bytespp); +} + +#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 0000000..33209cf --- /dev/null +++ b/drivers/staging/omapdrm/omap_encoder.c @@ -0,0 +1,188 @@ +/* + * 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 "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; +} + +/* 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 0000000..82ed612 --- /dev/null +++ b/drivers/staging/omapdrm/omap_fb.c @@ -0,0 +1,261 @@ +/* + * 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 "omap_drv.h" + +#include "drm_crtc.h" +#include "drm_crtc_helper.h" + + +/* + * framebuffer funcs + */ + +#define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) + +struct omap_framebuffer { + struct drm_framebuffer base; + struct drm_gem_object *bo; + int size; + dma_addr_t paddr; +}; + +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); + return drm_gem_handle_create(file_priv, omap_fb->bo, handle); +} + +static void omap_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct drm_device *dev = fb->dev; + 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_gem_put_paddr(omap_fb->bo)) { + dev_err(dev->dev, "could not unmap!\n"); + } + + if (omap_fb->bo) { + drm_gem_object_unreference_unlocked(omap_fb->bo); + } + + 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, +}; + +/* returns the buffer size */ +int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, + void **vaddr, dma_addr_t *paddr, unsigned int *screen_width) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + int bpp = fb->bits_per_pixel / 8; + unsigned long offset; + + offset = (x * bpp) + (y * fb->pitch); + + if (vaddr) { + void *bo_vaddr = omap_gem_vaddr(omap_fb->bo); + /* note: we can only count on having a vaddr for buffers that + * are allocated physically contiguously to begin with (ie. + * dma_alloc_coherent()). But this should be ok because it + * is only used by legacy fbdev + */ + BUG_ON(!bo_vaddr); + *vaddr = bo_vaddr + offset; + } + + *paddr = omap_fb->paddr + offset; + *screen_width = fb->pitch / bpp; + + return omap_fb->size - offset; +} + +struct drm_gem_object *omap_framebuffer_bo(struct drm_framebuffer *fb) +{ + struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); + return omap_fb->bo; +} + +/* 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; +} + +/* 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); + } + } +} + +struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, + struct drm_file *file, struct drm_mode_fb_cmd *mode_cmd) +{ + struct drm_gem_object *bo; + struct drm_framebuffer *fb; + bo = drm_gem_object_lookup(dev, file, mode_cmd->handle); + if (!bo) { + return ERR_PTR(-ENOENT); + } + fb = omap_framebuffer_init(dev, mode_cmd, bo); + if (!fb) { + return ERR_PTR(-ENOMEM); + } + return fb; +} + +struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd *mode_cmd, struct drm_gem_object *bo) +{ + struct omap_framebuffer *omap_fb; + struct drm_framebuffer *fb = NULL; + int size, ret; + + DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%d)", + dev, mode_cmd, mode_cmd->width, mode_cmd->height, + mode_cmd->bpp); + + /* in case someone tries to feed us a completely bogus stride: */ + mode_cmd->pitch = align_pitch(mode_cmd->pitch, + mode_cmd->width, mode_cmd->bpp); + + 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); + + if (bo) { + DBG("using existing %d byte buffer (needed %d)", bo->size, size); + if (size > bo->size) { + dev_err(dev->dev, "provided buffer object is too small!\n"); + goto fail; + } + } else { + /* for convenience of all the various callers who don't want + * to be bothered to allocate their own buffer.. + */ + union omap_gem_size gsize = { + .bytes = size, + }; + DBG("allocating %d bytes for fb %d", size, dev->primary->index); + bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); + if (!bo) { + dev_err(dev->dev, "failed to allocate buffer object\n"); + goto fail; + } + } + + omap_fb->bo = bo; + omap_fb->size = size; + + if (omap_gem_get_paddr(bo, &omap_fb->paddr, true)) { + dev_err(dev->dev, "could not map (paddr)!\n"); + goto fail; + } + + drm_helper_mode_fill_fb_struct(fb, mode_cmd); + + return fb; + +fail: + if (fb) { + omap_framebuffer_destroy(fb); + } + return NULL; +} diff --git a/drivers/staging/omapdrm/omap_fbdev.c b/drivers/staging/omapdrm/omap_fbdev.c new file mode 100644 index 0000000..048077c --- /dev/null +++ b/drivers/staging/omapdrm/omap_fbdev.c @@ -0,0 +1,304 @@ +/* + * 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 "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 = NULL; + struct fb_info *fbi = NULL; + struct drm_mode_fb_cmd mode_cmd = {0}; + dma_addr_t 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; + + fb = omap_framebuffer_init(dev, &mode_cmd, NULL); + if (!fb) { + dev_err(dev->dev, "failed to allocate fb\n"); + ret = -ENOMEM; + goto fail; + } + + 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_unlock; + } + + DBG("fbi=%p, dev=%p", fbi, dev); + + fbdev->fb = 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_unlock; + } + + 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_unlock: + mutex_unlock(&dev->struct_mutex); +fail: + + if (ret) { + if (fbi) + framebuffer_release(fbi); + if (fb) + fb->funcs->destroy(fb); + } + + 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"); +} + +static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc, + u16 *red, u16 *green, u16 *blue, int regno) +{ + DBG("fbdev: get gamma"); +} + +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); +} + +/* 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: + kfree(fbdev); + return NULL; +} + +void omap_fbdev_free(struct drm_device *dev) +{ + struct omap_drm_private *priv = dev->dev_private; + struct drm_fb_helper *helper = priv->fbdev; + struct omap_fbdev *fbdev; + struct fb_info *fbi; + + DBG(); + + fbi = helper->fbdev; + + unregister_framebuffer(fbi); + framebuffer_release(fbi); + + drm_fb_helper_fini(helper); + + fbdev = to_omap_fbdev(priv->fbdev); + + kfree(fbdev); + + priv->fbdev = NULL; +} diff --git a/drivers/staging/omapdrm/omap_gem.c b/drivers/staging/omapdrm/omap_gem.c new file mode 100644 index 0000000..3210059 --- /dev/null +++ b/drivers/staging/omapdrm/omap_gem.c @@ -0,0 +1,769 @@ +/* + * drivers/staging/omapdrm/omap_gem.c + * + * Copyright (C) 2011 Texas Instruments + * Author: Rob Clark <rob.clark@linaro.org> + * + * 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/spinlock.h> +#include <linux/shmem_fs.h> + +#include "omap_drv.h" + +/* + * GEM buffer object implementation. + */ + +#define to_omap_bo(x) container_of(x, struct omap_gem_object, base) + +/* note: we use upper 8 bits of flags for driver-internal flags: */ +#define OMAP_BO_DMA 0x01000000 /* actually is physically contiguous */ +#define OMAP_BO_EXT_SYNC 0x02000000 /* externally allocated sync object */ +#define OMAP_BO_EXT_MEM 0x04000000 /* externally allocated memory */ + + +struct omap_gem_object { + struct drm_gem_object base; + + uint32_t flags; + + /** + * If buffer is allocated physically contiguous, the OMAP_BO_DMA flag + * is set and the paddr is valid. + * + * Note that OMAP_BO_SCANOUT is a hint from userspace that DMA capable + * buffer is requested, but doesn't mean that it is. Use the + * OMAP_BO_DMA flag to determine if the buffer has a DMA capable + * physical address. + */ + dma_addr_t paddr; + + /** + * Array of backing pages, if allocated. Note that pages are never + * allocated for buffers originally allocated from contiguous memory + */ + struct page **pages; + + /** + * Virtual address, if mapped. + */ + void *vaddr; + + /** + * sync-object allocated on demand (if needed) + * + * Per-buffer sync-object for tracking pending and completed hw/dma + * read and write operations. The layout in memory is dictated by + * the SGX firmware, which uses this information to stall the command + * stream if a surface is not ready yet. + * + * Note that when buffer is used by SGX, the sync-object needs to be + * allocated from a special heap of sync-objects. This way many sync + * objects can be packed in a page, and not waste GPU virtual address + * space. Because of this we have to have a omap_gem_set_sync_object() + * API to allow replacement of the syncobj after it has (potentially) + * already been allocated. A bit ugly but I haven't thought of a + * better alternative. + */ + struct { + uint32_t write_pending; + uint32_t write_complete; + uint32_t read_pending; + uint32_t read_complete; + } *sync; +}; + +/* GEM objects can either be allocated from contiguous memory (in which + * case obj->filp==NULL), or w/ shmem backing (obj->filp!=NULL). But non + * contiguous buffers can be remapped in TILER/DMM if they need to be + * contiguous... but we don't do this all the time to reduce pressure + * on TILER/DMM space when we know at allocation time that the buffer + * will need to be scanned out. + */ +static inline bool is_shmem(struct drm_gem_object *obj) +{ + return obj->filp != NULL; +} + +static int get_pages(struct drm_gem_object *obj, struct page ***pages); + +static DEFINE_SPINLOCK(sync_lock); + +/** ensure backing pages are allocated */ +static int omap_gem_attach_pages(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct page **pages; + + WARN_ON(omap_obj->pages); + + /* TODO: __GFP_DMA32 .. but somehow GFP_HIGHMEM is coming from the + * mapping_gfp_mask(mapping) which conflicts w/ GFP_DMA32.. probably + * we actually want CMA memory for it all anyways.. + */ + pages = drm_gem_get_pages(obj, GFP_KERNEL); + if (IS_ERR(pages)) { + dev_err(obj->dev->dev, "could not get pages: %ld\n", PTR_ERR(pages)); + return PTR_ERR(pages); + } + + omap_obj->pages = pages; + return 0; +} + +/** release backing pages */ +static void omap_gem_detach_pages(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + drm_gem_put_pages(obj, omap_obj->pages, true, false); + omap_obj->pages = NULL; +} + +/** get mmap offset */ +uint64_t omap_gem_mmap_offset(struct drm_gem_object *obj) +{ + if (!obj->map_list.map) { + /* Make it mmapable */ + int ret = drm_gem_create_mmap_offset(obj); + if (ret) { + dev_err(obj->dev->dev, "could not allocate mmap offset"); + return 0; + } + } + + return (uint64_t)obj->map_list.hash.key << PAGE_SHIFT; +} + +/** + * omap_gem_fault - pagefault handler for GEM objects + * @vma: the VMA of the GEM object + * @vmf: fault detail + * + * Invoked when a fault occurs on an mmap of a GEM managed area. GEM + * does most of the work for us including the actual map/unmap calls + * but we need to do the actual page work. + * + * The VMA was set up by GEM. In doing so it also ensured that the + * vma->vm_private_data points to the GEM object that is backing this + * mapping. + */ +int omap_gem_fault(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct drm_gem_object *obj = vma->vm_private_data; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + struct drm_device *dev = obj->dev; + struct page **pages; + unsigned long pfn; + pgoff_t pgoff; + int ret; + + /* Make sure we don't parallel update on a fault, nor move or remove + * something from beneath our feet + */ + mutex_lock(&dev->struct_mutex); + + /* if a shmem backed object, make sure we have pages attached now */ + ret = get_pages(obj, &pages); + if (ret) { + goto fail; + } + + /* where should we do corresponding put_pages().. we are mapping + * the original page, rather than thru a GART, so we can't rely + * on eviction to trigger this. But munmap() or all mappings should + * probably trigger put_pages()? + */ + + /* We don't use vmf->pgoff since that has the fake offset: */ + pgoff = ((unsigned long)vmf->virtual_address - + vma->vm_start) >> PAGE_SHIFT; + + if (omap_obj->pages) { + pfn = page_to_pfn(omap_obj->pages[pgoff]); + } else { + BUG_ON(!(omap_obj->flags & OMAP_BO_DMA)); + pfn = (omap_obj->paddr >> PAGE_SHIFT) + pgoff; + } + + VERB("Inserting %p pfn %lx, pa %lx", vmf->virtual_address, + pfn, pfn << PAGE_SHIFT); + + ret = vm_insert_mixed(vma, (unsigned long)vmf->virtual_address, pfn); + +fail: + mutex_unlock(&dev->struct_mutex); + switch (ret) { + case 0: + case -ERESTARTSYS: + case -EINTR: + return VM_FAULT_NOPAGE; + case -ENOMEM: + return VM_FAULT_OOM; + default: + return VM_FAULT_SIGBUS; + } +} + +/** We override mainly to fix up some of the vm mapping flags.. */ +int omap_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + struct omap_gem_object *omap_obj; + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) { + DBG("mmap failed: %d", ret); + return ret; + } + + /* after drm_gem_mmap(), it is safe to access the obj */ + omap_obj = to_omap_bo(vma->vm_private_data); + + vma->vm_flags &= ~VM_PFNMAP; + vma->vm_flags |= VM_MIXEDMAP; + + if (omap_obj->flags & OMAP_BO_WC) { + vma->vm_page_prot = pgprot_writecombine(vm_get_page_prot(vma->vm_flags)); + } else if (omap_obj->flags & OMAP_BO_UNCACHED) { + vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); + } else { + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + } + + return ret; +} + +/** + * omap_gem_dumb_create - create a dumb buffer + * @drm_file: our client file + * @dev: our device + * @args: the requested arguments copied from userspace + * + * Allocate a buffer suitable for use for a frame buffer of the + * form described by user space. Give userspace a handle by which + * to reference it. + */ +int omap_gem_dumb_create(struct drm_file *file, struct drm_device *dev, + struct drm_mode_create_dumb *args) +{ + union omap_gem_size gsize; + + /* in case someone tries to feed us a completely bogus stride: */ + args->pitch = align_pitch(args->pitch, args->width, args->bpp); + args->size = PAGE_ALIGN(args->pitch * args->height); + + gsize = (union omap_gem_size){ + .bytes = args->size, + }; + + return omap_gem_new_handle(dev, file, gsize, + OMAP_BO_SCANOUT | OMAP_BO_WC, &args->handle); +} + +/** + * omap_gem_dumb_destroy - destroy a dumb buffer + * @file: client file + * @dev: our DRM device + * @handle: the object handle + * + * Destroy a handle that was created via omap_gem_dumb_create. + */ +int omap_gem_dumb_destroy(struct drm_file *file, struct drm_device *dev, + uint32_t handle) +{ + /* No special work needed, drop the reference and see what falls out */ + return drm_gem_handle_delete(file, handle); +} + +/** + * omap_gem_dumb_map - buffer mapping for dumb interface + * @file: our drm client file + * @dev: drm device + * @handle: GEM handle to the object (from dumb_create) + * + * Do the necessary setup to allow the mapping of the frame buffer + * into user memory. We don't have to do much here at the moment. + */ +int omap_gem_dumb_map_offset(struct drm_file *file, struct drm_device *dev, + uint32_t handle, uint64_t *offset) +{ + struct drm_gem_object *obj; + int ret = 0; + + mutex_lock(&dev->struct_mutex); + + /* GEM does all our handle to object mapping */ + obj = drm_gem_object_lookup(dev, file, handle); + if (obj == NULL) { + ret = -ENOENT; + goto fail; + } + + *offset = omap_gem_mmap_offset(obj); + + drm_gem_object_unreference_unlocked(obj); + +fail: + mutex_unlock(&dev->struct_mutex); + return ret; +} + +/* Get physical address for DMA.. if 'remap' is true, and the buffer is not + * already contiguous, remap it to pin in physically contiguous memory.. (ie. + * map in TILER) + */ +int omap_gem_get_paddr(struct drm_gem_object *obj, + dma_addr_t *paddr, bool remap) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + if (is_shmem(obj)) { + /* TODO: remap to TILER */ + return -ENOMEM; + } + + *paddr = omap_obj->paddr; + + return ret; +} + +/* Release physical address, when DMA is no longer being performed.. this + * could potentially unpin and unmap buffers from TILER + */ +int omap_gem_put_paddr(struct drm_gem_object *obj) +{ + /* do something here when remap to TILER is used.. */ + return 0; +} + +/* acquire pages when needed (for example, for DMA where physically + * contiguous buffer is not required + */ +static int get_pages(struct drm_gem_object *obj, struct page ***pages) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + if (is_shmem(obj) && !omap_obj->pages) { + ret = omap_gem_attach_pages(obj); + if (ret) { + dev_err(obj->dev->dev, "could not attach pages\n"); + return ret; + } + } + + /* TODO: even phys-contig.. we should have a list of pages? */ + *pages = omap_obj->pages; + + return 0; +} + +int omap_gem_get_pages(struct drm_gem_object *obj, struct page ***pages) +{ + int ret; + mutex_lock(&obj->dev->struct_mutex); + ret = get_pages(obj, pages); + mutex_unlock(&obj->dev->struct_mutex); + return ret; +} + +/* release pages when DMA no longer being performed */ +int omap_gem_put_pages(struct drm_gem_object *obj) +{ + /* do something here if we dynamically attach/detach pages.. at + * least they would no longer need to be pinned if everyone has + * released the pages.. + */ + return 0; +} + +/* Get kernel virtual address for CPU access.. only buffers that are + * allocated contiguously have a kernel virtual address, so this more + * or less only exists for omap_fbdev + */ +void *omap_gem_vaddr(struct drm_gem_object *obj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + return omap_obj->vaddr; +} + +/* Buffer Synchronization: + */ + +struct omap_gem_sync_waiter { + struct list_head list; + struct omap_gem_object *omap_obj; + enum omap_gem_op op; + uint32_t read_target, write_target; + /* notify called w/ sync_lock held */ + void (*notify)(void *arg); + void *arg; +}; + +/* list of omap_gem_sync_waiter.. the notify fxn gets called back when + * the read and/or write target count is achieved which can call a user + * callback (ex. to kick 3d and/or 2d), wakeup blocked task (prep for + * cpu access), etc. + */ +static LIST_HEAD(waiters); + +static inline bool is_waiting(struct omap_gem_sync_waiter *waiter) +{ + struct omap_gem_object *omap_obj = waiter->omap_obj; + if ((waiter->op & OMAP_GEM_READ) && + (omap_obj->sync->read_complete < waiter->read_target)) + return true; + if ((waiter->op & OMAP_GEM_WRITE) && + (omap_obj->sync->write_complete < waiter->write_target)) + return true; + return false; +} + +/* macro for sync debug.. */ +#define SYNCDBG 0 +#define SYNC(fmt, ...) do { if (SYNCDBG) \ + printk(KERN_ERR "%s:%d: "fmt"\n", \ + __func__, __LINE__, ##__VA_ARGS__); \ + } while (0) + + +static void sync_op_update(void) +{ + struct omap_gem_sync_waiter *waiter, *n; + list_for_each_entry_safe(waiter, n, &waiters, list) { + if (!is_waiting(waiter)) { + list_del(&waiter->list); + SYNC("notify: %p", waiter); + waiter->notify(waiter->arg); + kfree(waiter); + } + } +} + +static inline int sync_op(struct drm_gem_object *obj, + enum omap_gem_op op, bool start) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + spin_lock(&sync_lock); + + if (!omap_obj->sync) { + omap_obj->sync = kzalloc(sizeof(*omap_obj->sync), GFP_ATOMIC); + if (!omap_obj->sync) { + ret = -ENOMEM; + goto unlock; + } + } + + if (start) { + if (op & OMAP_GEM_READ) + omap_obj->sync->read_pending++; + if (op & OMAP_GEM_WRITE) + omap_obj->sync->write_pending++; + } else { + if (op & OMAP_GEM_READ) + omap_obj->sync->read_complete++; + if (op & OMAP_GEM_WRITE) + omap_obj->sync->write_complete++; + sync_op_update(); + } + +unlock: + spin_unlock(&sync_lock); + + return ret; +} + +/* it is a bit lame to handle updates in this sort of polling way, but + * in case of PVR, the GPU can directly update read/write complete + * values, and not really tell us which ones it updated.. this also + * means that sync_lock is not quite sufficient. So we'll need to + * do something a bit better when it comes time to add support for + * separate 2d hw.. + */ +void omap_gem_op_update(void) +{ + spin_lock(&sync_lock); + sync_op_update(); + spin_unlock(&sync_lock); +} + +/* mark the start of read and/or write operation */ +int omap_gem_op_start(struct drm_gem_object *obj, enum omap_gem_op op) +{ + return sync_op(obj, op, true); +} + +int omap_gem_op_finish(struct drm_gem_object *obj, enum omap_gem_op op) +{ + return sync_op(obj, op, false); +} + +static DECLARE_WAIT_QUEUE_HEAD(sync_event); + +static void sync_notify(void *arg) +{ + struct task_struct **waiter_task = arg; + *waiter_task = NULL; + wake_up_all(&sync_event); +} + +int omap_gem_op_sync(struct drm_gem_object *obj, enum omap_gem_op op) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + if (omap_obj->sync) { + struct task_struct *waiter_task = current; + struct omap_gem_sync_waiter *waiter = + kzalloc(sizeof(*waiter), GFP_KERNEL); + + if (!waiter) { + return -ENOMEM; + } + + waiter->omap_obj = omap_obj; + waiter->op = op; + waiter->read_target = omap_obj->sync->read_pending; + waiter->write_target = omap_obj->sync->write_pending; + waiter->notify = sync_notify; + waiter->arg = &waiter_task; + + spin_lock(&sync_lock); + if (is_waiting(waiter)) { + SYNC("waited: %p", waiter); + list_add_tail(&waiter->list, &waiters); + spin_unlock(&sync_lock); + ret = wait_event_interruptible(sync_event, + (waiter_task == NULL)); + spin_lock(&sync_lock); + if (waiter_task) { + SYNC("interrupted: %p", waiter); + /* we were interrupted */ + list_del(&waiter->list); + waiter_task = NULL; + } else { + /* freed in sync_op_update() */ + waiter = NULL; + } + } + spin_unlock(&sync_lock); + + if (waiter) { + kfree(waiter); + } + } + return ret; +} + +/* call fxn(arg), either synchronously or asynchronously if the op + * is currently blocked.. fxn() can be called from any context + * + * (TODO for now fxn is called back from whichever context calls + * omap_gem_op_update().. but this could be better defined later + * if needed) + * + * TODO more code in common w/ _sync().. + */ +int omap_gem_op_async(struct drm_gem_object *obj, enum omap_gem_op op, + void (*fxn)(void *arg), void *arg) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + if (omap_obj->sync) { + struct omap_gem_sync_waiter *waiter = + kzalloc(sizeof(*waiter), GFP_ATOMIC); + + if (!waiter) { + return -ENOMEM; + } + + waiter->omap_obj = omap_obj; + waiter->op = op; + waiter->read_target = omap_obj->sync->read_pending; + waiter->write_target = omap_obj->sync->write_pending; + waiter->notify = fxn; + waiter->arg = arg; + + spin_lock(&sync_lock); + if (is_waiting(waiter)) { + SYNC("waited: %p", waiter); + list_add_tail(&waiter->list, &waiters); + spin_unlock(&sync_lock); + return 0; + } + + spin_unlock(&sync_lock); + } + + /* no waiting.. */ + fxn(arg); + + return 0; +} + +/* special API so PVR can update the buffer to use a sync-object allocated + * from it's sync-obj heap. Only used for a newly allocated (from PVR's + * perspective) sync-object, so we overwrite the new syncobj w/ values + * from the already allocated syncobj (if there is one) + */ +int omap_gem_set_sync_object(struct drm_gem_object *obj, void *syncobj) +{ + struct omap_gem_object *omap_obj = to_omap_bo(obj); + int ret = 0; + + spin_lock(&sync_lock); + + if ((omap_obj->flags & OMAP_BO_EXT_SYNC) && !syncobj) { + /* clearing a previously set syncobj */ + syncobj = kzalloc(sizeof(*omap_obj->sync), GFP_ATOMIC); + if (!syncobj) { + ret = -ENOMEM; + goto unlock; + } + memcpy(syncobj, omap_obj->sync, sizeof(*omap_obj->sync)); + omap_obj->flags &= ~OMAP_BO_EXT_SYNC; + omap_obj->sync = syncobj; + } else if (syncobj && !(omap_obj->flags & OMAP_BO_EXT_SYNC)) { + /* replacing an existing syncobj */ + if (omap_obj->sync) { + memcpy(syncobj, omap_obj->sync, sizeof(*omap_obj->sync)); + kfree(omap_obj->sync); + } + omap_obj->flags |= OMAP_BO_EXT_SYNC; + omap_obj->sync = syncobj; + } + +unlock: + spin_unlock(&sync_lock); + return ret; +} + +int omap_gem_init_object(struct drm_gem_object *obj) +{ + return -EINVAL; /* unused */ +} + +/* don't call directly.. called from GEM core when it is time to actually + * free the object.. + */ +void omap_gem_free_object(struct drm_gem_object *obj) +{ + struct drm_device *dev = obj->dev; + struct omap_gem_object *omap_obj = to_omap_bo(obj); + + if (obj->map_list.map) { + drm_gem_free_mmap_offset(obj); + } + + /* don't free externally allocated backing memory */ + if (!(omap_obj->flags & OMAP_BO_EXT_MEM)) { + if (omap_obj->pages) { + omap_gem_detach_pages(obj); + } + if (!is_shmem(obj)) { + dma_free_writecombine(dev->dev, obj->size, + omap_obj->vaddr, omap_obj->paddr); + } + } + + /* don't free externally allocated syncobj */ + if (!(omap_obj->flags & OMAP_BO_EXT_SYNC)) { + kfree(omap_obj->sync); + } + + drm_gem_object_release(obj); + + kfree(obj); +} + +/* convenience method to construct a GEM buffer object, and userspace handle */ +int omap_gem_new_handle(struct drm_device *dev, struct drm_file *file, + union omap_gem_size gsize, uint32_t flags, uint32_t *handle) +{ + struct drm_gem_object *obj; + int ret; + + obj = omap_gem_new(dev, gsize, flags); + if (!obj) + return -ENOMEM; + + ret = drm_gem_handle_create(file, obj, handle); + if (ret) { + drm_gem_object_release(obj); + kfree(obj); /* TODO isn't there a dtor to call? just copying i915 */ + return ret; + } + + /* drop reference from allocate - handle holds it now */ + drm_gem_object_unreference_unlocked(obj); + + return 0; +} + +/* GEM buffer object constructor */ +struct drm_gem_object *omap_gem_new(struct drm_device *dev, + union omap_gem_size gsize, uint32_t flags) +{ + struct omap_gem_object *omap_obj; + struct drm_gem_object *obj = NULL; + size_t size; + int ret; + + if (flags & OMAP_BO_TILED) { + /* TODO: not implemented yet */ + goto fail; + } + + size = PAGE_ALIGN(gsize.bytes); + + omap_obj = kzalloc(sizeof(*omap_obj), GFP_KERNEL); + if (!omap_obj) { + dev_err(dev->dev, "could not allocate GEM object\n"); + goto fail; + } + + obj = &omap_obj->base; + + if (flags & OMAP_BO_SCANOUT) { + /* attempt to allocate contiguous memory */ + omap_obj->vaddr = dma_alloc_writecombine(dev->dev, size, + &omap_obj->paddr, GFP_KERNEL); + if (omap_obj->vaddr) { + flags |= OMAP_BO_DMA; + } + } + + omap_obj->flags = flags; + + if (flags & (OMAP_BO_DMA|OMAP_BO_EXT_MEM)) { + ret = drm_gem_private_object_init(dev, obj, size); + } else { + ret = drm_gem_object_init(dev, obj, size); + } + + if (ret) { + goto fail; + } + + return obj; + +fail: + if (obj) { + omap_gem_free_object(obj); + } + return NULL; +} diff --git a/drivers/video/omap2/omapfb/Kconfig b/drivers/video/omap2/omapfb/Kconfig index aa33386..4ea17dc 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 + tristate "OMAP2+ frame buffer support" + depends on FB && OMAP2_DSS && !DRM_OMAP select OMAP2_VRAM select OMAP2_VRFB if ARCH_OMAP2 || ARCH_OMAP3 diff --git a/include/drm/Kbuild b/include/drm/Kbuild index 3a60ac8..78d451c 100644 --- a/include/drm/Kbuild +++ b/include/drm/Kbuild @@ -11,3 +11,4 @@ header-y += savage_drm.h header-y += sis_drm.h header-y += via_drm.h header-y += vmwgfx_drm.h +header-y += omap_drm.h diff --git a/include/drm/omap_drm.h b/include/drm/omap_drm.h new file mode 100644 index 0000000..40167dd --- /dev/null +++ b/include/drm/omap_drm.h @@ -0,0 +1,123 @@ +/* + * include/drm/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 "drm.h" + +/* Please note that modifications to all structs defined here are + * subject to backwards-compatibility constraints. + */ + +#define OMAP_PARAM_CHIPSET_ID 1 /* ie. 0x3430, 0x4430, etc */ + +struct drm_omap_param { + uint64_t param; /* in */ + uint64_t value; /* in (set_param), out (get_param) */ +}; + +#define OMAP_BO_SCANOUT 0x00000001 /* scanout capable (phys contiguous) */ +#define OMAP_BO_CACHE_MASK 0x00000006 /* cache type mask, see cache modes */ +#define OMAP_BO_TILED_MASK 0x00000f00 /* tiled mapping mask, see tiled modes */ + +/* cache modes */ +#define OMAP_BO_CACHED 0x00000000 /* default */ +#define OMAP_BO_WC 0x00000002 /* write-combine */ +#define OMAP_BO_UNCACHED 0x00000004 /* strongly-ordered (uncached) */ + +/* tiled modes */ +#define OMAP_BO_TILED_8 0x00000100 +#define OMAP_BO_TILED_16 0x00000200 +#define OMAP_BO_TILED_32 0x00000300 +#define OMAP_BO_TILED (OMAP_BO_TILED_8 | OMAP_BO_TILED_16 | OMAP_BO_TILED_32) + +union omap_gem_size { + uint32_t bytes; /* (for non-tiled formats) */ + struct { + uint16_t width; + uint16_t height; + } tiled; /* (for tiled formats) */ +}; + +struct drm_omap_gem_new { + union omap_gem_size size; /* in */ + uint32_t flags; /* in */ + uint32_t handle; /* out */ + uint32_t __pad; +}; + +/* mask of operations: */ +enum omap_gem_op { + OMAP_GEM_READ = 0x01, + OMAP_GEM_WRITE = 0x02, +}; + +struct drm_omap_gem_cpu_prep { + uint32_t handle; /* buffer handle (in) */ + uint32_t op; /* mask of omap_gem_op (in) */ +}; + +struct drm_omap_gem_cpu_fini { + uint32_t handle; /* buffer handle (in) */ + uint32_t op; /* mask of omap_gem_op (in) */ + /* TODO maybe here we pass down info about what regions are touched + * by sw so we can be clever about cache ops? For now a placeholder, + * set to zero and we just do full buffer flush.. + */ + uint32_t nregions; + uint32_t __pad; +}; + +struct drm_omap_gem_info { + uint32_t handle; /* buffer handle (in) */ + uint32_t pad; + uint64_t offset; /* mmap offset (out) */ + /* note: in case of tiled buffers, the user virtual size can be + * different from the physical size (ie. how many pages are needed + * to back the object) which is returned in DRM_IOCTL_GEM_OPEN.. + * This size here is the one that should be used if you want to + * mmap() the buffer: + */ + uint32_t size; /* virtual size for mmap'ing (out) */ + uint32_t __pad; +}; + +#define DRM_OMAP_GET_PARAM 0x00 +#define DRM_OMAP_SET_PARAM 0x01 +/* placeholder for plugin-api +#define DRM_OMAP_GET_BASE 0x02 +*/ +#define DRM_OMAP_GEM_NEW 0x03 +#define DRM_OMAP_GEM_CPU_PREP 0x04 +#define DRM_OMAP_GEM_CPU_FINI 0x05 +#define DRM_OMAP_GEM_INFO 0x06 +#define DRM_OMAP_NUM_IOCTLS 0x07 + +#define DRM_IOCTL_OMAP_GET_PARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GET_PARAM, struct drm_omap_param) +#define DRM_IOCTL_OMAP_SET_PARAM DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_SET_PARAM, struct drm_omap_param) +/* placeholder for plugin-api +#define DRM_IOCTL_OMAP_GET_BASE DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GET_BASE, struct drm_omap_get_base) +*/ +#define DRM_IOCTL_OMAP_GEM_NEW DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GEM_NEW, struct drm_omap_gem_new) +#define DRM_IOCTL_OMAP_GEM_CPU_PREP DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_GEM_CPU_PREP, struct drm_omap_gem_cpu_prep) +#define DRM_IOCTL_OMAP_GEM_CPU_FINI DRM_IOW (DRM_COMMAND_BASE + DRM_OMAP_GEM_CPU_FINI, struct drm_omap_gem_cpu_fini) +#define DRM_IOCTL_OMAP_GEM_INFO DRM_IOWR(DRM_COMMAND_BASE + DRM_OMAP_GEM_INFO, struct drm_omap_gem_info) + +#endif /* __OMAP_DRM_H__ */ diff --git a/include/drm/omap_priv.h b/include/drm/omap_priv.h new file mode 100644 index 0000000..f482d1e --- /dev/null +++ b/include/drm/omap_priv.h @@ -0,0 +1,42 @@ +/* + * include/drm/omap_priv.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_PRIV_H__ +#define __OMAP_PRIV_H__ + +/* Non-userspace facing APIs + */ + +/* 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__ */