diff options
| author | Rob Clark <rob@ti.com> | 2011-09-01 03:05:21 -0400 |
|---|---|---|
| committer | Paolo Pisati <paolo.pisati@canonical.com> | 2012-08-17 04:18:47 -0400 |
| commit | 6ac5d0b980b1a9c43071cb1ab36aaf29bff6fd45 (patch) | |
| tree | 3df728f370191334e723ffb715f153248e4ac8a4 /drivers/staging | |
| parent | 07d9aa8a852d1e4437ed23a8d84926b4e0580f05 (diff) | |
Add omap drm display driver
A DSS based DRM display driver, which provides a plugin interface for
3d/2d accelerators to register. The core driver handles construction
of CRTC/encoder/connectors to represent the hardware, and support KMS.
This driver replaces omapfb. The omap drm driver allocates framebuffer
memory and implements (with the help of drm_fb_helper) the legacy fbdev
interface.
The driver maps CRTCs to overlays, encoders to overlay-managers, and
connectors to dssdev's.
Signed-off-by: Rob Clark <rob@ti.com>
Signed-off-by: Ricardo Salveti de Araujo <ricardo.salveti@canonical.com>
Diffstat (limited to 'drivers/staging')
| -rw-r--r-- | drivers/staging/Kconfig | 2 | ||||
| -rw-r--r-- | drivers/staging/Makefile | 1 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/Kconfig | 25 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/Makefile | 8 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/TODO.txt | 10 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/omap_connector.c | 398 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/omap_crtc.c | 325 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/omap_drv.c | 751 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/omap_drv.h | 68 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/omap_encoder.c | 198 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/omap_fb.c | 251 | ||||
| -rw-r--r-- | drivers/staging/omapdrm/omap_fbdev.c | 279 |
12 files changed, 2316 insertions, 0 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 04d35130e9f..a9a422575bb 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig | |||
| @@ -162,4 +162,6 @@ source "drivers/staging/mei/Kconfig" | |||
| 162 | 162 | ||
| 163 | source "drivers/staging/nvec/Kconfig" | 163 | source "drivers/staging/nvec/Kconfig" |
| 164 | 164 | ||
| 165 | source "drivers/staging/omapdrm/Kconfig" | ||
| 166 | |||
| 165 | endif # STAGING | 167 | endif # STAGING |
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index db56670ec3a..db8a6500b67 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile | |||
| @@ -72,3 +72,4 @@ obj-$(CONFIG_TOUCHSCREEN_SYNAPTICS_I2C_RMI4) += ste_rmi4/ | |||
| 72 | obj-$(CONFIG_DRM_PSB) += gma500/ | 72 | obj-$(CONFIG_DRM_PSB) += gma500/ |
| 73 | obj-$(CONFIG_INTEL_MEI) += mei/ | 73 | obj-$(CONFIG_INTEL_MEI) += mei/ |
| 74 | obj-$(CONFIG_MFD_NVEC) += nvec/ | 74 | obj-$(CONFIG_MFD_NVEC) += nvec/ |
| 75 | obj-$(CONFIG_DRM_OMAP) += omapdrm/ | ||
diff --git a/drivers/staging/omapdrm/Kconfig b/drivers/staging/omapdrm/Kconfig new file mode 100644 index 00000000000..513ff916075 --- /dev/null +++ b/drivers/staging/omapdrm/Kconfig | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | |||
| 2 | config DRM_OMAP | ||
| 3 | tristate "OMAP DRM (EXPERIMENTAL)" | ||
| 4 | depends on DRM && !CONFIG_FB_OMAP2 | ||
| 5 | select DRM_KMS_HELPER | ||
| 6 | select OMAP2_VRAM | ||
| 7 | select OMAP2_DSS | ||
| 8 | select FB_SYS_FILLRECT | ||
| 9 | select FB_SYS_COPYAREA | ||
| 10 | select FB_SYS_IMAGEBLIT | ||
| 11 | select FB_SYS_FOPS | ||
| 12 | default y | ||
| 13 | help | ||
| 14 | DRM display driver for OMAP2/3/4 based boards. | ||
| 15 | |||
| 16 | config DRM_OMAP_NUM_CRTCS | ||
| 17 | int "Number of CRTCs" | ||
| 18 | range 1 10 | ||
| 19 | default 1 if ARCH_OMAP2 || ARCH_OMAP3 | ||
| 20 | default 2 if ARCH_OMAP4 | ||
| 21 | depends on DRM_OMAP | ||
| 22 | help | ||
| 23 | Select the number of video overlays which can be used as framebuffers. | ||
| 24 | The remaining overlays are reserved for video. | ||
| 25 | |||
diff --git a/drivers/staging/omapdrm/Makefile b/drivers/staging/omapdrm/Makefile new file mode 100644 index 00000000000..0fd44de2ce3 --- /dev/null +++ b/drivers/staging/omapdrm/Makefile | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | # | ||
| 2 | # Makefile for the drm device driver. This driver provides support for the | ||
| 3 | # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. | ||
| 4 | |||
| 5 | ccflags-y := -Iinclude/drm -Werror | ||
| 6 | omapdrm-y := omap_drv.o omap_crtc.o omap_encoder.o omap_connector.o omap_fb.o omap_fbdev.o | ||
| 7 | |||
| 8 | obj-$(CONFIG_DRM_OMAP) += omapdrm.o | ||
diff --git a/drivers/staging/omapdrm/TODO.txt b/drivers/staging/omapdrm/TODO.txt new file mode 100644 index 00000000000..65a2c8239a2 --- /dev/null +++ b/drivers/staging/omapdrm/TODO.txt | |||
| @@ -0,0 +1,10 @@ | |||
| 1 | + check error handling/cleanup paths | ||
| 2 | + GEM buffer support.. don't ignore bo id in omap_fb | ||
| 3 | |||
| 4 | + plugins should register consecutive ioctl's relative to a base ioctl | ||
| 5 | # that is assigned by omap_drm | ||
| 6 | + omap_drm should have an ioctl to query by plugin name the assigned | ||
| 7 | base ioctl # | ||
| 8 | + userspace should use this base ioctl # to calculate the actual ioctl | ||
| 9 | # relative to that base. | ||
| 10 | |||
diff --git a/drivers/staging/omapdrm/omap_connector.c b/drivers/staging/omapdrm/omap_connector.c new file mode 100644 index 00000000000..f0c30213609 --- /dev/null +++ b/drivers/staging/omapdrm/omap_connector.c | |||
| @@ -0,0 +1,398 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/staging/omapdrm/omap_connector.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 Texas Instruments | ||
| 5 | * Author: Rob Clark <rob@ti.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify it | ||
| 8 | * under the terms of the GNU General Public License version 2 as published by | ||
| 9 | * the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 14 | * more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License along with | ||
| 17 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #include <linux/omap_drm.h> | ||
| 21 | #include "omap_drv.h" | ||
| 22 | |||
| 23 | #include "drm_crtc.h" | ||
| 24 | #include "drm_crtc_helper.h" | ||
| 25 | |||
| 26 | /* | ||
| 27 | * connector funcs | ||
| 28 | */ | ||
| 29 | |||
| 30 | #define to_omap_connector(x) container_of(x, struct omap_connector, base) | ||
| 31 | |||
| 32 | struct omap_connector { | ||
| 33 | struct drm_connector base; | ||
| 34 | struct omap_dss_device *dssdev; | ||
| 35 | }; | ||
| 36 | |||
| 37 | static inline void copy_timings_omap_to_drm(struct drm_display_mode *mode, | ||
| 38 | struct omap_video_timings *timings) | ||
| 39 | { | ||
| 40 | mode->clock = timings->pixel_clock; | ||
| 41 | |||
| 42 | mode->hdisplay = timings->x_res; | ||
| 43 | mode->hsync_start = mode->hdisplay + timings->hfp; | ||
| 44 | mode->hsync_end = mode->hsync_start + timings->hsw; | ||
| 45 | mode->htotal = mode->hsync_end + timings->hbp; | ||
| 46 | |||
| 47 | mode->vdisplay = timings->y_res; | ||
| 48 | mode->vsync_start = mode->vdisplay + timings->vfp; | ||
| 49 | mode->vsync_end = mode->vsync_start + timings->vsw; | ||
| 50 | mode->vtotal = mode->vsync_end + timings->vbp; | ||
| 51 | |||
| 52 | /* note: whether or not it is interlaced, +/- h/vsync, etc, | ||
| 53 | * which should be set in the mode flags, is not exposed in | ||
| 54 | * the omap_video_timings struct.. but hdmi driver tracks | ||
| 55 | * those separately so all we have to have to set the mode | ||
| 56 | * is the way to recover these timings values, and the | ||
| 57 | * omap_dss_driver would do the rest. | ||
| 58 | */ | ||
| 59 | } | ||
| 60 | |||
| 61 | static inline void copy_timings_drm_to_omap(struct omap_video_timings *timings, | ||
| 62 | struct drm_display_mode *mode) | ||
| 63 | { | ||
| 64 | timings->pixel_clock = mode->clock; | ||
| 65 | |||
| 66 | timings->x_res = mode->hdisplay; | ||
| 67 | timings->hfp = mode->hsync_start - mode->hdisplay; | ||
| 68 | timings->hsw = mode->hsync_end - mode->hsync_start; | ||
| 69 | timings->hbp = mode->htotal - mode->hsync_end; | ||
| 70 | |||
| 71 | timings->y_res = mode->vdisplay; | ||
| 72 | timings->vfp = mode->vsync_start - mode->vdisplay; | ||
| 73 | timings->vsw = mode->vsync_end - mode->vsync_start; | ||
| 74 | timings->vbp = mode->vtotal - mode->vsync_end; | ||
| 75 | } | ||
| 76 | |||
| 77 | void omap_connector_dpms(struct drm_connector *connector, int mode) | ||
| 78 | { | ||
| 79 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 80 | struct omap_dss_device *dssdev = omap_connector->dssdev; | ||
| 81 | |||
| 82 | /* TODO: add API in DSS to suspend/resume individual displays.. */ | ||
| 83 | |||
| 84 | DBG("%s: %d", dssdev->name, mode); | ||
| 85 | } | ||
| 86 | |||
| 87 | enum drm_connector_status omap_connector_detect( | ||
| 88 | struct drm_connector *connector, bool force) | ||
| 89 | { | ||
| 90 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 91 | struct omap_dss_device *dssdev = omap_connector->dssdev; | ||
| 92 | struct omap_dss_driver *dssdrv = dssdev->driver; | ||
| 93 | enum drm_connector_status ret; | ||
| 94 | |||
| 95 | if (dssdrv->is_detected(dssdev)) { | ||
| 96 | ret = connector_status_connected; | ||
| 97 | } else { | ||
| 98 | ret = connector_status_disconnected; | ||
| 99 | } | ||
| 100 | |||
| 101 | VERB("%s: %d (force=%d)", omap_connector->dssdev->name, ret, force); | ||
| 102 | |||
| 103 | return ret; | ||
| 104 | } | ||
| 105 | |||
| 106 | static void omap_connector_destroy(struct drm_connector *connector) | ||
| 107 | { | ||
| 108 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 109 | |||
| 110 | DBG("%s", omap_connector->dssdev->name); | ||
| 111 | drm_sysfs_connector_remove(connector); | ||
| 112 | drm_connector_cleanup(connector); | ||
| 113 | kfree(omap_connector); | ||
| 114 | } | ||
| 115 | |||
| 116 | #define MAX_EDID 256 | ||
| 117 | |||
| 118 | static int omap_connector_get_modes(struct drm_connector *connector) | ||
| 119 | { | ||
| 120 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 121 | struct omap_dss_device *dssdev = omap_connector->dssdev; | ||
| 122 | struct omap_dss_driver *dssdrv = dssdev->driver; | ||
| 123 | struct drm_device *dev = connector->dev; | ||
| 124 | int n = 0; | ||
| 125 | |||
| 126 | DBG("%s", omap_connector->dssdev->name); | ||
| 127 | |||
| 128 | /* if display exposes EDID, then we parse that in the normal way to | ||
| 129 | * build table of supported modes.. otherwise (ie. fixed resolution | ||
| 130 | * LCD panels) we just return a single mode corresponding to the | ||
| 131 | * currently configured timings: | ||
| 132 | */ | ||
| 133 | if (dssdrv->get_edid) { | ||
| 134 | void *edid = kzalloc(MAX_EDID, GFP_KERNEL); | ||
| 135 | |||
| 136 | if ((dssdrv->get_edid(dssdev, edid, MAX_EDID) == 0) && | ||
| 137 | drm_edid_is_valid(edid)) { | ||
| 138 | drm_mode_connector_update_edid_property(connector, edid); | ||
| 139 | n = drm_add_edid_modes(connector, edid); | ||
| 140 | kfree(connector->display_info.raw_edid); | ||
| 141 | connector->display_info.raw_edid = edid; | ||
| 142 | } else { | ||
| 143 | drm_mode_connector_update_edid_property(connector, NULL); | ||
| 144 | connector->display_info.raw_edid = NULL; | ||
| 145 | kfree(edid); | ||
| 146 | } | ||
| 147 | } else { | ||
| 148 | struct drm_display_mode *mode = drm_mode_create(dev); | ||
| 149 | struct omap_video_timings timings; | ||
| 150 | |||
| 151 | dssdrv->get_timings(dssdev, &timings); | ||
| 152 | |||
| 153 | copy_timings_omap_to_drm(mode, &timings); | ||
| 154 | |||
| 155 | mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED; | ||
| 156 | drm_mode_set_name(mode); | ||
| 157 | drm_mode_probed_add(connector, mode); | ||
| 158 | |||
| 159 | n = 1; | ||
| 160 | } | ||
| 161 | |||
| 162 | return n; | ||
| 163 | } | ||
| 164 | |||
| 165 | static int omap_connector_mode_valid(struct drm_connector *connector, | ||
| 166 | struct drm_display_mode *mode) | ||
| 167 | { | ||
| 168 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 169 | struct omap_dss_device *dssdev = omap_connector->dssdev; | ||
| 170 | struct omap_dss_driver *dssdrv = dssdev->driver; | ||
| 171 | struct omap_video_timings timings = {0}; | ||
| 172 | struct drm_device *dev = connector->dev; | ||
| 173 | struct drm_display_mode *new_mode; | ||
| 174 | int ret = MODE_BAD; | ||
| 175 | |||
| 176 | copy_timings_drm_to_omap(&timings, mode); | ||
| 177 | mode->vrefresh = drm_mode_vrefresh(mode); | ||
| 178 | |||
| 179 | if (!dssdrv->check_timings(dssdev, &timings)) { | ||
| 180 | /* check if vrefresh is still valid */ | ||
| 181 | new_mode = drm_mode_duplicate(dev, mode); | ||
| 182 | new_mode->clock = timings.pixel_clock; | ||
| 183 | new_mode->vrefresh = 0; | ||
| 184 | if (mode->vrefresh == drm_mode_vrefresh(new_mode)) | ||
| 185 | ret = MODE_OK; | ||
| 186 | drm_mode_destroy(dev, new_mode); | ||
| 187 | } | ||
| 188 | |||
| 189 | DBG("connector: mode %s: " | ||
| 190 | "%d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", | ||
| 191 | (ret == MODE_OK) ? "valid" : "invalid", | ||
| 192 | mode->base.id, mode->name, mode->vrefresh, mode->clock, | ||
| 193 | mode->hdisplay, mode->hsync_start, | ||
| 194 | mode->hsync_end, mode->htotal, | ||
| 195 | mode->vdisplay, mode->vsync_start, | ||
| 196 | mode->vsync_end, mode->vtotal, mode->type, mode->flags); | ||
| 197 | |||
| 198 | return ret; | ||
| 199 | } | ||
| 200 | |||
| 201 | struct drm_encoder * omap_connector_attached_encoder( | ||
| 202 | struct drm_connector *connector) | ||
| 203 | { | ||
| 204 | int i; | ||
| 205 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 206 | |||
| 207 | for (i = 0; i < DRM_CONNECTOR_MAX_ENCODER; i++) { | ||
| 208 | struct drm_mode_object *obj; | ||
| 209 | |||
| 210 | if (connector->encoder_ids[i] == 0) | ||
| 211 | break; | ||
| 212 | |||
| 213 | obj = drm_mode_object_find(connector->dev, | ||
| 214 | connector->encoder_ids[i], | ||
| 215 | DRM_MODE_OBJECT_ENCODER); | ||
| 216 | |||
| 217 | if (obj) { | ||
| 218 | struct drm_encoder *encoder = obj_to_encoder(obj); | ||
| 219 | struct omap_overlay_manager *mgr = | ||
| 220 | omap_encoder_get_manager(encoder); | ||
| 221 | DBG("%s: found %s", omap_connector->dssdev->name, | ||
| 222 | mgr->name); | ||
| 223 | return encoder; | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | DBG("%s: no encoder", omap_connector->dssdev->name); | ||
| 228 | |||
| 229 | return NULL; | ||
| 230 | } | ||
| 231 | |||
| 232 | static const struct drm_connector_funcs omap_connector_funcs = { | ||
| 233 | .dpms = drm_helper_connector_dpms, | ||
| 234 | .detect = omap_connector_detect, | ||
| 235 | .fill_modes = drm_helper_probe_single_connector_modes, | ||
| 236 | .destroy = omap_connector_destroy, | ||
| 237 | }; | ||
| 238 | |||
| 239 | static const struct drm_connector_helper_funcs omap_connector_helper_funcs = { | ||
| 240 | .get_modes = omap_connector_get_modes, | ||
| 241 | .mode_valid = omap_connector_mode_valid, | ||
| 242 | .best_encoder = omap_connector_attached_encoder, | ||
| 243 | }; | ||
| 244 | |||
| 245 | /* called from encoder when mode is set, to propagate settings to the dssdev */ | ||
| 246 | void omap_connector_mode_set(struct drm_connector *connector, | ||
| 247 | struct drm_display_mode *mode) | ||
| 248 | { | ||
| 249 | struct drm_device *dev = connector->dev; | ||
| 250 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 251 | struct omap_dss_device *dssdev = omap_connector->dssdev; | ||
| 252 | struct omap_dss_driver *dssdrv = dssdev->driver; | ||
| 253 | struct omap_video_timings timings; | ||
| 254 | |||
| 255 | copy_timings_drm_to_omap(&timings, mode); | ||
| 256 | |||
| 257 | DBG("%s: set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", | ||
| 258 | omap_connector->dssdev->name, | ||
| 259 | mode->base.id, mode->name, mode->vrefresh, mode->clock, | ||
| 260 | mode->hdisplay, mode->hsync_start, | ||
| 261 | mode->hsync_end, mode->htotal, | ||
| 262 | mode->vdisplay, mode->vsync_start, | ||
| 263 | mode->vsync_end, mode->vtotal, mode->type, mode->flags); | ||
| 264 | |||
| 265 | if (dssdrv->check_timings(dssdev, &timings)) { | ||
| 266 | dev_err(dev->dev, "could not set timings\n"); | ||
| 267 | return; | ||
| 268 | } | ||
| 269 | |||
| 270 | dssdrv->set_timings(dssdev, &timings); | ||
| 271 | } | ||
| 272 | |||
| 273 | enum omap_dss_update_mode omap_connector_get_update_mode( | ||
| 274 | struct drm_connector *connector) | ||
| 275 | { | ||
| 276 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 277 | struct omap_dss_device *dssdev = omap_connector->dssdev; | ||
| 278 | struct omap_dss_driver *dssdrv = dssdev->driver; | ||
| 279 | |||
| 280 | DBG("%s", omap_connector->dssdev->name); | ||
| 281 | |||
| 282 | if (dssdrv->get_update_mode) { | ||
| 283 | return dssdrv->get_update_mode(dssdev); | ||
| 284 | } | ||
| 285 | |||
| 286 | return -1; | ||
| 287 | } | ||
| 288 | EXPORT_SYMBOL(omap_connector_get_update_mode); | ||
| 289 | |||
| 290 | int omap_connector_set_update_mode(struct drm_connector *connector, | ||
| 291 | enum omap_dss_update_mode mode) | ||
| 292 | { | ||
| 293 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 294 | struct omap_dss_device *dssdev = omap_connector->dssdev; | ||
| 295 | struct omap_dss_driver *dssdrv = dssdev->driver; | ||
| 296 | |||
| 297 | DBG("%s: %d", omap_connector->dssdev->name, mode); | ||
| 298 | |||
| 299 | if (dssdrv->set_update_mode) { | ||
| 300 | return dssdrv->set_update_mode(dssdev, mode); | ||
| 301 | } | ||
| 302 | |||
| 303 | return -EINVAL; | ||
| 304 | } | ||
| 305 | EXPORT_SYMBOL(omap_connector_set_update_mode); | ||
| 306 | |||
| 307 | int omap_connector_sync(struct drm_connector *connector) | ||
| 308 | { | ||
| 309 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 310 | struct omap_dss_device *dssdev = omap_connector->dssdev; | ||
| 311 | struct omap_dss_driver *dssdrv = dssdev->driver; | ||
| 312 | |||
| 313 | DBG("%s", omap_connector->dssdev->name); | ||
| 314 | |||
| 315 | if (dssdrv->sync) { | ||
| 316 | return dssdrv->sync(dssdev); | ||
| 317 | } | ||
| 318 | |||
| 319 | return -EINVAL; | ||
| 320 | } | ||
| 321 | EXPORT_SYMBOL(omap_connector_sync); | ||
| 322 | |||
| 323 | /* flush an area of the framebuffer (in case of manual update display that | ||
| 324 | * is not automatically flushed) | ||
| 325 | */ | ||
| 326 | void omap_connector_flush(struct drm_connector *connector, | ||
| 327 | int x, int y, int w, int h) | ||
| 328 | { | ||
| 329 | struct omap_connector *omap_connector = to_omap_connector(connector); | ||
| 330 | |||
| 331 | /* TODO: enable when supported in dss */ | ||
| 332 | VERB("%s: %d,%d, %dx%d", omap_connector->dssdev->name, x, y, w, h); | ||
| 333 | } | ||
| 334 | |||
| 335 | /* initialize connector */ | ||
| 336 | struct drm_connector * omap_connector_init(struct drm_device *dev, | ||
| 337 | int connector_type, struct omap_dss_device *dssdev) | ||
| 338 | { | ||
| 339 | struct drm_connector *connector = NULL; | ||
| 340 | struct omap_connector *omap_connector; | ||
| 341 | |||
| 342 | DBG("%s", dssdev->name); | ||
| 343 | |||
| 344 | omap_connector = kzalloc(sizeof(struct omap_connector), GFP_KERNEL); | ||
| 345 | if (!omap_connector) { | ||
| 346 | dev_err(dev->dev, "could not allocate connector\n"); | ||
| 347 | goto fail; | ||
| 348 | } | ||
| 349 | |||
| 350 | omap_connector->dssdev = dssdev; | ||
| 351 | connector = &omap_connector->base; | ||
| 352 | |||
| 353 | drm_connector_init(dev, connector, &omap_connector_funcs, | ||
| 354 | connector_type); | ||
| 355 | drm_connector_helper_add(connector, &omap_connector_helper_funcs); | ||
| 356 | |||
| 357 | #if 0 /* enable when dss2 supports hotplug */ | ||
| 358 | if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_HPD) | ||
| 359 | connector->polled = 0; | ||
| 360 | else | ||
| 361 | #endif | ||
| 362 | connector->polled = DRM_CONNECTOR_POLL_CONNECT | | ||
| 363 | DRM_CONNECTOR_POLL_DISCONNECT; | ||
| 364 | |||
| 365 | connector->interlace_allowed = 1; | ||
| 366 | connector->doublescan_allowed = 0; | ||
| 367 | |||
| 368 | drm_sysfs_connector_add(connector); | ||
| 369 | |||
| 370 | /* store resume info for suspended displays */ | ||
| 371 | switch (dssdev->state) { | ||
| 372 | case OMAP_DSS_DISPLAY_SUSPENDED: | ||
| 373 | dssdev->activate_after_resume = true; | ||
| 374 | break; | ||
| 375 | case OMAP_DSS_DISPLAY_DISABLED: | ||
| 376 | if (dssdev->driver) { | ||
| 377 | int ret = dssdev->driver->enable(dssdev); | ||
| 378 | if (ret) { | ||
| 379 | DBG("%s: failed to enable: %d", | ||
| 380 | dssdev->name, ret); | ||
| 381 | dssdev->driver->disable(dssdev); | ||
| 382 | goto fail; | ||
| 383 | } | ||
| 384 | } | ||
| 385 | break; | ||
| 386 | default: | ||
| 387 | break; | ||
| 388 | } | ||
| 389 | |||
| 390 | return connector; | ||
| 391 | |||
| 392 | fail: | ||
| 393 | if (connector) { | ||
| 394 | omap_connector_destroy(connector); | ||
| 395 | } | ||
| 396 | |||
| 397 | return NULL; | ||
| 398 | } | ||
diff --git a/drivers/staging/omapdrm/omap_crtc.c b/drivers/staging/omapdrm/omap_crtc.c new file mode 100644 index 00000000000..98c263373e8 --- /dev/null +++ b/drivers/staging/omapdrm/omap_crtc.c | |||
| @@ -0,0 +1,325 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/staging/omapdrm/omap_crtc.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 Texas Instruments | ||
| 5 | * Author: Rob Clark <rob@ti.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify it | ||
| 8 | * under the terms of the GNU General Public License version 2 as published by | ||
| 9 | * the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 14 | * more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License along with | ||
| 17 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #include <linux/omap_drm.h> | ||
| 21 | #include "omap_drv.h" | ||
| 22 | |||
| 23 | #include "drm_mode.h" | ||
| 24 | #include "drm_crtc.h" | ||
| 25 | #include "drm_crtc_helper.h" | ||
| 26 | |||
| 27 | #define to_omap_crtc(x) container_of(x, struct omap_crtc, base) | ||
| 28 | |||
| 29 | struct omap_crtc { | ||
| 30 | struct drm_crtc base; | ||
| 31 | struct omap_overlay *ovl; | ||
| 32 | struct omap_overlay_info info; | ||
| 33 | int id; | ||
| 34 | }; | ||
| 35 | |||
| 36 | /* push changes down to dss2 */ | ||
| 37 | static int commit(struct drm_crtc *crtc) | ||
| 38 | { | ||
| 39 | struct drm_device *dev = crtc->dev; | ||
| 40 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 41 | struct omap_overlay *ovl = omap_crtc->ovl; | ||
| 42 | struct omap_overlay_info *info = &omap_crtc->info; | ||
| 43 | int ret; | ||
| 44 | |||
| 45 | DBG("%s", omap_crtc->ovl->name); | ||
| 46 | DBG("%dx%d -> %dx%d (%d)", info->width, info->height, info->out_width, | ||
| 47 | info->out_height, info->screen_width); | ||
| 48 | DBG("%d,%d %p %08x", info->pos_x, info->pos_y, info->vaddr, | ||
| 49 | info->paddr); | ||
| 50 | |||
| 51 | /* NOTE: do we want to do this at all here, or just wait | ||
| 52 | * for dpms(ON) since other CRTC's may not have their mode | ||
| 53 | * set yet, so fb dimensions may still change.. | ||
| 54 | */ | ||
| 55 | ret = ovl->set_overlay_info(ovl, info); | ||
| 56 | if (ret) { | ||
| 57 | dev_err(dev->dev, "could not set overlay info\n"); | ||
| 58 | return ret; | ||
| 59 | } | ||
| 60 | |||
| 61 | /* our encoder doesn't necessarily get a commit() after this, in | ||
| 62 | * particular in the dpms() and mode_set_base() cases, so force the | ||
| 63 | * manager to update: | ||
| 64 | * | ||
| 65 | * could this be in the encoder somehow? | ||
| 66 | */ | ||
| 67 | if (ovl->manager) { | ||
| 68 | ret = ovl->manager->apply(ovl->manager); | ||
| 69 | if (ret) { | ||
| 70 | dev_err(dev->dev, "could not apply\n"); | ||
| 71 | return ret; | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | if (info->enabled) { | ||
| 76 | omap_framebuffer_flush(crtc->fb, crtc->x, crtc->y, | ||
| 77 | crtc->fb->width, crtc->fb->height); | ||
| 78 | } | ||
| 79 | |||
| 80 | return 0; | ||
| 81 | } | ||
| 82 | |||
| 83 | /* update parameters that are dependent on the framebuffer dimensions and | ||
| 84 | * position within the fb that this crtc scans out from. This is called | ||
| 85 | * when framebuffer dimensions or x,y base may have changed, either due | ||
| 86 | * to our mode, or a change in another crtc that is scanning out of the | ||
| 87 | * same fb. | ||
| 88 | */ | ||
| 89 | static void update_scanout(struct drm_crtc *crtc) | ||
| 90 | { | ||
| 91 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 92 | unsigned long paddr; | ||
| 93 | void __iomem *vaddr; | ||
| 94 | int screen_width; | ||
| 95 | |||
| 96 | omap_framebuffer_get_buffer(crtc->fb, crtc->x, crtc->y, | ||
| 97 | &vaddr, &paddr, &screen_width); | ||
| 98 | |||
| 99 | DBG("%s: %d,%d: %p %08lx (%d)", omap_crtc->ovl->name, | ||
| 100 | crtc->x, crtc->y, vaddr, paddr, screen_width); | ||
| 101 | |||
| 102 | omap_crtc->info.paddr = paddr; | ||
| 103 | omap_crtc->info.vaddr = vaddr; | ||
| 104 | omap_crtc->info.screen_width = screen_width; | ||
| 105 | } | ||
| 106 | |||
| 107 | static void omap_crtc_gamma_set(struct drm_crtc *crtc, | ||
| 108 | u16 *red, u16 *green, u16 *blue, uint32_t start, uint32_t size) | ||
| 109 | { | ||
| 110 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 111 | DBG("%s", omap_crtc->ovl->name); | ||
| 112 | // XXX ignore? | ||
| 113 | } | ||
| 114 | |||
| 115 | static void omap_crtc_destroy(struct drm_crtc *crtc) | ||
| 116 | { | ||
| 117 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 118 | DBG("%s", omap_crtc->ovl->name); | ||
| 119 | drm_crtc_cleanup(crtc); | ||
| 120 | kfree(omap_crtc); | ||
| 121 | } | ||
| 122 | |||
| 123 | static void omap_crtc_dpms(struct drm_crtc *crtc, int mode) | ||
| 124 | { | ||
| 125 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 126 | |||
| 127 | DBG("%s: %d", omap_crtc->ovl->name, mode); | ||
| 128 | |||
| 129 | if (mode == DRM_MODE_DPMS_ON) { | ||
| 130 | update_scanout(crtc); | ||
| 131 | omap_crtc->info.enabled = true; | ||
| 132 | } else { | ||
| 133 | omap_crtc->info.enabled = false; | ||
| 134 | } | ||
| 135 | |||
| 136 | commit(crtc); | ||
| 137 | } | ||
| 138 | |||
| 139 | static bool omap_crtc_mode_fixup(struct drm_crtc *crtc, | ||
| 140 | struct drm_display_mode *mode, | ||
| 141 | struct drm_display_mode *adjusted_mode) | ||
| 142 | { | ||
| 143 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 144 | // XXX I guess we support anything? | ||
| 145 | DBG("%s", omap_crtc->ovl->name); | ||
| 146 | return true; | ||
| 147 | } | ||
| 148 | |||
| 149 | static int omap_crtc_mode_set(struct drm_crtc *crtc, | ||
| 150 | struct drm_display_mode *mode, | ||
| 151 | struct drm_display_mode *adjusted_mode, | ||
| 152 | int x, int y, | ||
| 153 | struct drm_framebuffer *old_fb) | ||
| 154 | { | ||
| 155 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 156 | |||
| 157 | DBG("%s: %d,%d: %dx%d",omap_crtc->ovl->name, x, y, | ||
| 158 | mode->hdisplay, mode->vdisplay); | ||
| 159 | |||
| 160 | /* just use adjusted mode */ | ||
| 161 | mode = adjusted_mode; | ||
| 162 | |||
| 163 | omap_crtc->info.width = mode->hdisplay; | ||
| 164 | omap_crtc->info.height = mode->vdisplay; | ||
| 165 | omap_crtc->info.out_width = mode->hdisplay; | ||
| 166 | omap_crtc->info.out_height = mode->vdisplay; | ||
| 167 | omap_crtc->info.color_mode = OMAP_DSS_COLOR_RGB24U; | ||
| 168 | omap_crtc->info.rotation_type = OMAP_DSS_ROT_DMA; | ||
| 169 | omap_crtc->info.rotation = OMAP_DSS_ROT_0; | ||
| 170 | omap_crtc->info.global_alpha = 0xff; | ||
| 171 | omap_crtc->info.mirror = 0; | ||
| 172 | omap_crtc->info.mirror = 0; | ||
| 173 | omap_crtc->info.pos_x = 0; | ||
| 174 | omap_crtc->info.pos_y = 0; | ||
| 175 | #if 0 /* re-enable when these are available in DSS2 driver */ | ||
| 176 | omap_crtc->info.zorder = 3; /* GUI in the front, video behind */ | ||
| 177 | omap_crtc->info.min_x_decim = 1; | ||
| 178 | omap_crtc->info.max_x_decim = 1; | ||
| 179 | omap_crtc->info.min_y_decim = 1; | ||
| 180 | omap_crtc->info.max_y_decim = 1; | ||
| 181 | #endif | ||
| 182 | |||
| 183 | update_scanout(crtc); | ||
| 184 | |||
| 185 | return 0; | ||
| 186 | } | ||
| 187 | |||
| 188 | static void omap_crtc_prepare(struct drm_crtc *crtc) | ||
| 189 | { | ||
| 190 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 191 | struct omap_overlay *ovl = omap_crtc->ovl; | ||
| 192 | |||
| 193 | DBG("%s", omap_crtc->ovl->name); | ||
| 194 | |||
| 195 | ovl->get_overlay_info(ovl, &omap_crtc->info); | ||
| 196 | |||
| 197 | omap_crtc_dpms(crtc, DRM_MODE_DPMS_OFF); | ||
| 198 | } | ||
| 199 | |||
| 200 | static void omap_crtc_commit(struct drm_crtc *crtc) | ||
| 201 | { | ||
| 202 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 203 | DBG("%s", omap_crtc->ovl->name); | ||
| 204 | omap_crtc_dpms(crtc, DRM_MODE_DPMS_ON); | ||
| 205 | } | ||
| 206 | |||
| 207 | static int omap_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, | ||
| 208 | struct drm_framebuffer *old_fb) | ||
| 209 | { | ||
| 210 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 211 | |||
| 212 | DBG("%s %d,%d: fb=%p", omap_crtc->ovl->name, x, y, old_fb); | ||
| 213 | |||
| 214 | update_scanout(crtc); | ||
| 215 | |||
| 216 | return commit(crtc); | ||
| 217 | } | ||
| 218 | |||
| 219 | static void omap_crtc_load_lut(struct drm_crtc *crtc) | ||
| 220 | { | ||
| 221 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 222 | DBG("%s", omap_crtc->ovl->name); | ||
| 223 | } | ||
| 224 | |||
| 225 | static int omap_crtc_page_flip_locked(struct drm_crtc *crtc, | ||
| 226 | struct drm_framebuffer *fb, | ||
| 227 | struct drm_pending_vblank_event *event) | ||
| 228 | { | ||
| 229 | struct drm_device *dev = crtc->dev; | ||
| 230 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 231 | struct timeval now; | ||
| 232 | unsigned long flags; | ||
| 233 | int ret; | ||
| 234 | |||
| 235 | DBG("%d -> %d", crtc->fb ? crtc->fb->base.id : -1, fb->base.id); | ||
| 236 | |||
| 237 | crtc->fb = fb; | ||
| 238 | |||
| 239 | update_scanout(crtc); | ||
| 240 | ret = commit(crtc); | ||
| 241 | |||
| 242 | /* wakeup userspace */ | ||
| 243 | // TODO: this should happen *after* flip.. somehow.. | ||
| 244 | if (event) { | ||
| 245 | spin_lock_irqsave(&dev->event_lock, flags); | ||
| 246 | event->event.sequence = | ||
| 247 | drm_vblank_count_and_time(dev, omap_crtc->id, &now); | ||
| 248 | event->event.tv_sec = now.tv_sec; | ||
| 249 | event->event.tv_usec = now.tv_usec; | ||
| 250 | list_add_tail(&event->base.link, | ||
| 251 | &event->base.file_priv->event_list); | ||
| 252 | wake_up_interruptible(&event->base.file_priv->event_wait); | ||
| 253 | spin_unlock_irqrestore(&dev->event_lock, flags); | ||
| 254 | } | ||
| 255 | |||
| 256 | return ret; | ||
| 257 | } | ||
| 258 | |||
| 259 | int omap_crtc_page_flip(struct drm_crtc *crtc, | ||
| 260 | struct drm_framebuffer *fb, | ||
| 261 | struct drm_pending_vblank_event *event) | ||
| 262 | { | ||
| 263 | struct drm_device *dev = crtc->dev; | ||
| 264 | int ret; | ||
| 265 | |||
| 266 | mutex_lock(&dev->mode_config.mutex); | ||
| 267 | ret = omap_crtc_page_flip_locked(crtc, fb, event); | ||
| 268 | mutex_unlock(&dev->mode_config.mutex); | ||
| 269 | |||
| 270 | return ret; | ||
| 271 | } | ||
| 272 | EXPORT_SYMBOL(omap_crtc_page_flip); | ||
| 273 | |||
| 274 | static const struct drm_crtc_funcs omap_crtc_funcs = { | ||
| 275 | .gamma_set = omap_crtc_gamma_set, | ||
| 276 | .set_config = drm_crtc_helper_set_config, | ||
| 277 | .destroy = omap_crtc_destroy, | ||
| 278 | .page_flip = omap_crtc_page_flip_locked, | ||
| 279 | }; | ||
| 280 | |||
| 281 | static const struct drm_crtc_helper_funcs omap_crtc_helper_funcs = { | ||
| 282 | .dpms = omap_crtc_dpms, | ||
| 283 | .mode_fixup = omap_crtc_mode_fixup, | ||
| 284 | .mode_set = omap_crtc_mode_set, | ||
| 285 | .prepare = omap_crtc_prepare, | ||
| 286 | .commit = omap_crtc_commit, | ||
| 287 | .mode_set_base = omap_crtc_mode_set_base, | ||
| 288 | .load_lut = omap_crtc_load_lut, | ||
| 289 | }; | ||
| 290 | |||
| 291 | struct omap_overlay * omap_crtc_get_overlay(struct drm_crtc *crtc) | ||
| 292 | { | ||
| 293 | struct omap_crtc *omap_crtc = to_omap_crtc(crtc); | ||
| 294 | return omap_crtc->ovl; | ||
| 295 | } | ||
| 296 | |||
| 297 | /* initialize crtc */ | ||
| 298 | struct drm_crtc * omap_crtc_init(struct drm_device *dev, | ||
| 299 | struct omap_overlay *ovl, int id) | ||
| 300 | { | ||
| 301 | struct drm_crtc *crtc = NULL; | ||
| 302 | struct omap_crtc *omap_crtc = kzalloc(sizeof(*omap_crtc), GFP_KERNEL); | ||
| 303 | |||
| 304 | DBG("%s", ovl->name); | ||
| 305 | |||
| 306 | if (!omap_crtc) { | ||
| 307 | dev_err(dev->dev, "could not allocate CRTC\n"); | ||
| 308 | goto fail; | ||
| 309 | } | ||
| 310 | |||
| 311 | omap_crtc->ovl = ovl; | ||
| 312 | omap_crtc->id = id; | ||
| 313 | crtc = &omap_crtc->base; | ||
| 314 | drm_crtc_init(dev, crtc, &omap_crtc_funcs); | ||
| 315 | drm_crtc_helper_add(crtc, &omap_crtc_helper_funcs); | ||
| 316 | |||
| 317 | return crtc; | ||
| 318 | |||
| 319 | fail: | ||
| 320 | if (crtc) { | ||
| 321 | drm_crtc_cleanup(crtc); | ||
| 322 | kfree(omap_crtc); | ||
| 323 | } | ||
| 324 | return NULL; | ||
| 325 | } | ||
diff --git a/drivers/staging/omapdrm/omap_drv.c b/drivers/staging/omapdrm/omap_drv.c new file mode 100644 index 00000000000..fc2d4e9fa92 --- /dev/null +++ b/drivers/staging/omapdrm/omap_drv.c | |||
| @@ -0,0 +1,751 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/staging/omapdrm/omap_drv.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 Texas Instruments | ||
| 5 | * Author: Rob Clark <rob@ti.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify it | ||
| 8 | * under the terms of the GNU General Public License version 2 as published by | ||
| 9 | * the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 14 | * more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License along with | ||
| 17 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #include <linux/omap_drm.h> | ||
| 21 | #include "omap_drv.h" | ||
| 22 | |||
| 23 | #include "drm_crtc_helper.h" | ||
| 24 | #include "drm_fb_helper.h" | ||
| 25 | |||
| 26 | #define DRIVER_NAME MODULE_NAME | ||
| 27 | #define DRIVER_DESC "OMAP DRM" | ||
| 28 | #define DRIVER_DATE "20110403" | ||
| 29 | #define DRIVER_MAJOR 1 | ||
| 30 | #define DRIVER_MINOR 0 | ||
| 31 | #define DRIVER_PATCHLEVEL 0 | ||
| 32 | |||
| 33 | struct drm_device *drm_device; | ||
| 34 | |||
| 35 | /* TODO: think about how to handle more than one plugin.. ie. some ops | ||
| 36 | * me might want to stop on the first plugin that doesn't return an | ||
| 37 | * error, etc.. | ||
| 38 | */ | ||
| 39 | LIST_HEAD(plugin_list); | ||
| 40 | |||
| 41 | /* keep track of whether we are already loaded.. we may need to call | ||
| 42 | * plugin's load() if they register after we are already loaded | ||
| 43 | */ | ||
| 44 | static bool loaded = false; | ||
| 45 | |||
| 46 | /* | ||
| 47 | * mode config funcs | ||
| 48 | */ | ||
| 49 | |||
| 50 | /* Notes about mapping DSS and DRM entities: | ||
| 51 | * CRTC: overlay | ||
| 52 | * encoder: manager.. with some extension to allow one primary CRTC | ||
| 53 | * and zero or more video CRTC's to be mapped to one encoder? | ||
| 54 | * connector: dssdev.. manager can be attached/detached from different | ||
| 55 | * devices | ||
| 56 | */ | ||
| 57 | |||
| 58 | static void omap_fb_output_poll_changed(struct drm_device *dev) | ||
| 59 | { | ||
| 60 | struct omap_drm_private *priv = dev->dev_private; | ||
| 61 | DBG("dev=%p", dev); | ||
| 62 | if (priv->fbdev) { | ||
| 63 | drm_fb_helper_hotplug_event(priv->fbdev); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | static struct drm_mode_config_funcs omap_mode_config_funcs = { | ||
| 68 | .fb_create = omap_framebuffer_create, | ||
| 69 | .output_poll_changed = omap_fb_output_poll_changed, | ||
| 70 | }; | ||
| 71 | |||
| 72 | static int get_connector_type(struct omap_dss_device *dssdev) | ||
| 73 | { | ||
| 74 | switch (dssdev->type) { | ||
| 75 | case OMAP_DISPLAY_TYPE_HDMI: | ||
| 76 | return DRM_MODE_CONNECTOR_HDMIA; | ||
| 77 | case OMAP_DISPLAY_TYPE_DPI: | ||
| 78 | if (!strcmp(dssdev->name, "dvi")) | ||
| 79 | return DRM_MODE_CONNECTOR_DVID; | ||
| 80 | default: | ||
| 81 | return DRM_MODE_CONNECTOR_Unknown; | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | #if 0 /* enable when dss2 supports hotplug */ | ||
| 86 | static int omap_drm_notifier(struct notifier_block *nb, | ||
| 87 | unsigned long evt, void *arg) | ||
| 88 | { | ||
| 89 | switch (evt) { | ||
| 90 | case OMAP_DSS_SIZE_CHANGE: | ||
| 91 | case OMAP_DSS_HOTPLUG_CONNECT: | ||
| 92 | case OMAP_DSS_HOTPLUG_DISCONNECT: { | ||
| 93 | struct drm_device *dev = drm_device; | ||
| 94 | DBG("hotplug event: evt=%d, dev=%p", evt, dev); | ||
| 95 | if (dev) { | ||
| 96 | drm_sysfs_hotplug_event(dev); | ||
| 97 | } | ||
| 98 | return NOTIFY_OK; | ||
| 99 | } | ||
| 100 | default: /* don't care about other events for now */ | ||
| 101 | return NOTIFY_DONE; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | #endif | ||
| 105 | |||
| 106 | static void dump_video_chains(void) | ||
| 107 | { | ||
| 108 | int i; | ||
| 109 | |||
| 110 | DBG("dumping video chains: "); | ||
| 111 | for (i = 0; i < omap_dss_get_num_overlays(); i++) { | ||
| 112 | struct omap_overlay *ovl = omap_dss_get_overlay(i); | ||
| 113 | struct omap_overlay_manager *mgr = ovl->manager; | ||
| 114 | struct omap_dss_device *dssdev = mgr ? mgr->device : NULL; | ||
| 115 | if (dssdev) { | ||
| 116 | DBG("%d: %s -> %s -> %s", i, ovl->name, mgr->name, | ||
| 117 | dssdev->name); | ||
| 118 | } else if (mgr) { | ||
| 119 | DBG("%d: %s -> %s", i, ovl->name, mgr->name); | ||
| 120 | } else { | ||
| 121 | DBG("%d: %s", i, ovl->name); | ||
| 122 | } | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | static int omap_modeset_init(struct drm_device *dev) | ||
| 127 | { | ||
| 128 | const struct omap_drm_platform_data *pdata = dev->dev->platform_data; | ||
| 129 | struct omap_drm_private *priv = dev->dev_private; | ||
| 130 | struct omap_dss_device *dssdev = NULL; | ||
| 131 | int i, j; | ||
| 132 | unsigned int connected_connectors = 0; | ||
| 133 | |||
| 134 | /* create encoders for each manager */ | ||
| 135 | int create_encoder(int i) { | ||
| 136 | struct omap_overlay_manager *mgr = | ||
| 137 | omap_dss_get_overlay_manager(i); | ||
| 138 | struct drm_encoder *encoder = omap_encoder_init(dev, mgr); | ||
| 139 | |||
| 140 | if (!encoder) { | ||
| 141 | dev_err(dev->dev, "could not create encoder\n"); | ||
| 142 | return -ENOMEM; | ||
| 143 | } | ||
| 144 | |||
| 145 | priv->encoders[priv->num_encoders++] = encoder; | ||
| 146 | |||
| 147 | return 0; | ||
| 148 | } | ||
| 149 | |||
| 150 | /* create connectors for each display device */ | ||
| 151 | int create_connector(struct omap_dss_device *dssdev) { | ||
| 152 | static struct notifier_block *notifier; | ||
| 153 | struct drm_connector *connector; | ||
| 154 | |||
| 155 | if (!dssdev->driver) { | ||
| 156 | dev_warn(dev->dev, "%s has no driver.. skipping it\n", | ||
| 157 | dssdev->name); | ||
| 158 | return 0; | ||
| 159 | } | ||
| 160 | |||
| 161 | if (!(dssdev->driver->get_timings || | ||
| 162 | dssdev->driver->get_edid)) { | ||
| 163 | dev_warn(dev->dev, "%s driver does not support " | ||
| 164 | "get_timings or get_edid.. skipping it!\n", | ||
| 165 | dssdev->name); | ||
| 166 | return 0; | ||
| 167 | } | ||
| 168 | |||
| 169 | omap_dss_get_device(dssdev); | ||
| 170 | |||
| 171 | connector = omap_connector_init(dev, | ||
| 172 | get_connector_type(dssdev), dssdev); | ||
| 173 | |||
| 174 | if (!connector) { | ||
| 175 | dev_err(dev->dev, "could not create connector\n"); | ||
| 176 | return -ENOMEM; | ||
| 177 | } | ||
| 178 | |||
| 179 | /* track what is already connected.. rather than looping thru | ||
| 180 | * all connectors twice later, first for connected then for | ||
| 181 | * remainder (which could be a race condition if connected | ||
| 182 | * status changes) | ||
| 183 | */ | ||
| 184 | if (omap_connector_detect(connector, true) == | ||
| 185 | connector_status_connected) { | ||
| 186 | connected_connectors |= (1 << priv->num_connectors); | ||
| 187 | } | ||
| 188 | |||
| 189 | priv->connectors[priv->num_connectors++] = connector; | ||
| 190 | |||
| 191 | #if 0 /* enable when dss2 supports hotplug */ | ||
| 192 | notifier = kzalloc(sizeof(struct notifier_block), GFP_KERNEL); | ||
| 193 | notifier->notifier_call = omap_drm_notifier; | ||
| 194 | omap_dss_add_notify(dssdev, notifier); | ||
| 195 | #else | ||
| 196 | notifier = NULL; | ||
| 197 | #endif | ||
| 198 | |||
| 199 | for (j = 0; j < priv->num_encoders; j++) { | ||
| 200 | struct omap_overlay_manager *mgr = | ||
| 201 | omap_encoder_get_manager(priv->encoders[j]); | ||
| 202 | if (mgr->device == dssdev) { | ||
| 203 | drm_mode_connector_attach_encoder(connector, | ||
| 204 | priv->encoders[j]); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | return 0; | ||
| 209 | } | ||
| 210 | |||
| 211 | /* create up to max_overlays CRTCs mapping to overlays.. by default, | ||
| 212 | * connect the overlays to different managers/encoders, giving priority | ||
| 213 | * to encoders connected to connectors with a detected connection | ||
| 214 | */ | ||
| 215 | int create_crtc(int i) { | ||
| 216 | struct omap_overlay *ovl = omap_dss_get_overlay(i); | ||
| 217 | struct omap_overlay_manager *mgr = NULL; | ||
| 218 | struct drm_crtc *crtc; | ||
| 219 | |||
| 220 | if (ovl->manager) { | ||
| 221 | DBG("disconnecting %s from %s", ovl->name, | ||
| 222 | ovl->manager->name); | ||
| 223 | ovl->unset_manager(ovl); | ||
| 224 | } | ||
| 225 | |||
| 226 | /* find next best connector, ones with detected connection first | ||
| 227 | */ | ||
| 228 | while (j < priv->num_connectors && !mgr) { | ||
| 229 | if (connected_connectors & (1 << j)) { | ||
| 230 | struct drm_encoder * encoder = | ||
| 231 | omap_connector_attached_encoder( | ||
| 232 | priv->connectors[j]); | ||
| 233 | if (encoder) { | ||
| 234 | mgr = omap_encoder_get_manager(encoder); | ||
| 235 | } | ||
| 236 | } | ||
| 237 | j++; | ||
| 238 | } | ||
| 239 | |||
| 240 | /* if we couldn't find another connected connector, lets start | ||
| 241 | * looking at the unconnected connectors: | ||
| 242 | */ | ||
| 243 | while (j < 2 * priv->num_connectors && !mgr) { | ||
| 244 | int idx = j - priv->num_connectors; | ||
| 245 | if (!(connected_connectors & (1 << idx))) { | ||
| 246 | struct drm_encoder * encoder = | ||
| 247 | omap_connector_attached_encoder( | ||
| 248 | priv->connectors[idx]); | ||
| 249 | if (encoder) { | ||
| 250 | mgr = omap_encoder_get_manager(encoder); | ||
| 251 | } | ||
| 252 | } | ||
| 253 | j++; | ||
| 254 | } | ||
| 255 | |||
| 256 | if (mgr) { | ||
| 257 | DBG("connecting %s to %s", ovl->name, mgr->name); | ||
| 258 | ovl->set_manager(ovl, mgr); | ||
| 259 | } | ||
| 260 | |||
| 261 | crtc = omap_crtc_init(dev, ovl, priv->num_crtcs); | ||
| 262 | |||
| 263 | if (!crtc) { | ||
| 264 | dev_err(dev->dev, "could not create CRTC\n"); | ||
| 265 | return -ENOMEM; | ||
| 266 | } | ||
| 267 | |||
| 268 | priv->crtcs[priv->num_crtcs++] = crtc; | ||
| 269 | |||
| 270 | return 0; | ||
| 271 | } | ||
| 272 | |||
| 273 | drm_mode_config_init(dev); | ||
| 274 | |||
| 275 | if (pdata) { | ||
| 276 | /* if platform data is provided by the board file, use it to | ||
| 277 | * control which overlays, managers, and devices we own. | ||
| 278 | */ | ||
| 279 | for (i = 0; i < pdata->mgr_cnt; i++) { | ||
| 280 | create_encoder(pdata->mgr_ids[i]); | ||
| 281 | } | ||
| 282 | |||
| 283 | for (i = 0; i < pdata->dev_cnt; i++) { | ||
| 284 | int m(struct omap_dss_device *dssdev, void *data) { | ||
| 285 | return ! strcmp(dssdev->name, data); | ||
| 286 | } | ||
| 287 | struct omap_dss_device *dssdev = | ||
| 288 | omap_dss_find_device( | ||
| 289 | (void *)pdata->dev_names[i], m); | ||
| 290 | if (!dssdev) { | ||
| 291 | dev_warn(dev->dev, "no such dssdev: %s\n", | ||
| 292 | pdata->dev_names[i]); | ||
| 293 | continue; | ||
| 294 | } | ||
| 295 | create_connector(dssdev); | ||
| 296 | } | ||
| 297 | |||
| 298 | j = 0; | ||
| 299 | for (i = 0; i < pdata->ovl_cnt; i++) { | ||
| 300 | create_crtc(pdata->ovl_ids[i]); | ||
| 301 | } | ||
| 302 | } else { | ||
| 303 | /* otherwise just grab up to CONFIG_DRM_OMAP_NUM_CRTCS and try | ||
| 304 | * to make educated guesses about everything else | ||
| 305 | */ | ||
| 306 | int max_overlays = min(omap_dss_get_num_overlays(), | ||
| 307 | CONFIG_DRM_OMAP_NUM_CRTCS); | ||
| 308 | |||
| 309 | for (i = 0; i < omap_dss_get_num_overlay_managers(); i++) { | ||
| 310 | create_encoder(i); | ||
| 311 | } | ||
| 312 | |||
| 313 | for_each_dss_dev(dssdev) { | ||
| 314 | create_connector(dssdev); | ||
| 315 | } | ||
| 316 | |||
| 317 | j = 0; | ||
| 318 | for (i = 0; i < max_overlays; i++) { | ||
| 319 | create_crtc(i); | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | /* for now keep the mapping of CRTCs and encoders static.. */ | ||
| 324 | for (i = 0; i < priv->num_encoders; i++) { | ||
| 325 | struct drm_encoder *encoder = priv->encoders[i]; | ||
| 326 | struct omap_overlay_manager *mgr = | ||
| 327 | omap_encoder_get_manager(encoder); | ||
| 328 | |||
| 329 | encoder->possible_crtcs = 0; | ||
| 330 | |||
| 331 | for (j = 0; j < priv->num_crtcs; j++) { | ||
| 332 | struct omap_overlay *ovl = | ||
| 333 | omap_crtc_get_overlay(priv->crtcs[j]); | ||
| 334 | if (ovl->manager == mgr) { | ||
| 335 | encoder->possible_crtcs |= (1 << j); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | DBG("%s: possible_crtcs=%08x", mgr->name, | ||
| 340 | encoder->possible_crtcs); | ||
| 341 | } | ||
| 342 | |||
| 343 | dump_video_chains(); | ||
| 344 | |||
| 345 | dev->mode_config.min_width = 640; | ||
| 346 | dev->mode_config.min_height = 480; | ||
| 347 | |||
| 348 | /* note: pvr can't currently handle dst surfaces larger than 2k by 2k */ | ||
| 349 | dev->mode_config.max_width = 2048; | ||
| 350 | dev->mode_config.max_height = 2048; | ||
| 351 | |||
| 352 | dev->mode_config.funcs = &omap_mode_config_funcs; | ||
| 353 | |||
| 354 | return 0; | ||
| 355 | } | ||
| 356 | |||
| 357 | /* | ||
| 358 | * drm driver funcs | ||
| 359 | */ | ||
| 360 | |||
| 361 | /** | ||
| 362 | * load - setup chip and create an initial config | ||
| 363 | * @dev: DRM device | ||
| 364 | * @flags: startup flags | ||
| 365 | * | ||
| 366 | * The driver load routine has to do several things: | ||
| 367 | * - initialize the memory manager | ||
| 368 | * - allocate initial config memory | ||
| 369 | * - setup the DRM framebuffer with the allocated memory | ||
| 370 | */ | ||
| 371 | static int dev_load(struct drm_device *dev, unsigned long flags) | ||
| 372 | { | ||
| 373 | struct omap_drm_private *priv; | ||
| 374 | struct omap_drm_plugin *plugin; | ||
| 375 | int ret; | ||
| 376 | |||
| 377 | DBG("load: dev=%p", dev); | ||
| 378 | |||
| 379 | drm_device = dev; | ||
| 380 | |||
| 381 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | ||
| 382 | if (!priv) { | ||
| 383 | dev_err(dev->dev, "could not allocate priv\n"); | ||
| 384 | return -1; | ||
| 385 | } | ||
| 386 | |||
| 387 | dev->dev_private = priv; | ||
| 388 | |||
| 389 | ret = omap_modeset_init(dev); | ||
| 390 | if (ret) { | ||
| 391 | dev_err(dev->dev, "omap_modeset_init failed: ret=%d\n", ret); | ||
| 392 | // hmm | ||
| 393 | //return ret; | ||
| 394 | } | ||
| 395 | |||
| 396 | priv->fbdev = omap_fbdev_init(dev); | ||
| 397 | if (!priv->fbdev) { | ||
| 398 | dev_err(dev->dev, "omap_fbdev_init failed\n"); | ||
| 399 | ret = -ENOMEM; | ||
| 400 | // hmm | ||
| 401 | //return ret; | ||
| 402 | } | ||
| 403 | |||
| 404 | drm_kms_helper_poll_init(dev); | ||
| 405 | |||
| 406 | ret = drm_vblank_init(dev, priv->num_crtcs); | ||
| 407 | if (ret) { | ||
| 408 | dev_err(dev->dev, "could not init vblank\n"); | ||
| 409 | } | ||
| 410 | |||
| 411 | loaded = true; | ||
| 412 | |||
| 413 | list_for_each_entry(plugin, &plugin_list, list) { | ||
| 414 | ret = plugin->load(dev, flags); | ||
| 415 | } | ||
| 416 | |||
| 417 | return 0; | ||
| 418 | } | ||
| 419 | |||
| 420 | static int dev_unload(struct drm_device *dev) | ||
| 421 | { | ||
| 422 | struct omap_drm_plugin *plugin; | ||
| 423 | int ret; | ||
| 424 | |||
| 425 | DBG("unload: dev=%p", dev); | ||
| 426 | |||
| 427 | list_for_each_entry(plugin, &plugin_list, list) { | ||
| 428 | ret = plugin->unload(dev); | ||
| 429 | } | ||
| 430 | |||
| 431 | drm_kms_helper_poll_fini(dev); | ||
| 432 | drm_vblank_cleanup(dev); | ||
| 433 | |||
| 434 | loaded = false; | ||
| 435 | |||
| 436 | return 0; | ||
| 437 | } | ||
| 438 | |||
| 439 | static int dev_open(struct drm_device *dev, struct drm_file *file) | ||
| 440 | { | ||
| 441 | struct omap_drm_plugin *plugin; | ||
| 442 | bool found_pvr = false; | ||
| 443 | int ret; | ||
| 444 | |||
| 445 | file->driver_priv = NULL; | ||
| 446 | |||
| 447 | DBG("open: dev=%p, file=%p", dev, file); | ||
| 448 | |||
| 449 | list_for_each_entry(plugin, &plugin_list, list) { | ||
| 450 | if (!strcmp(DRIVER_NAME "_pvr", plugin->name)) { | ||
| 451 | found_pvr = true; | ||
| 452 | break; | ||
| 453 | } | ||
| 454 | } | ||
| 455 | |||
| 456 | if (!found_pvr) { | ||
| 457 | DBG("open: PVR submodule not loaded.. let's try now"); | ||
| 458 | request_module(DRIVER_NAME "_pvr"); | ||
| 459 | } | ||
| 460 | |||
| 461 | list_for_each_entry(plugin, &plugin_list, list) { | ||
| 462 | ret = plugin->open(dev, file); | ||
| 463 | } | ||
| 464 | |||
| 465 | return 0; | ||
| 466 | } | ||
| 467 | |||
| 468 | static int dev_firstopen(struct drm_device *dev) | ||
| 469 | { | ||
| 470 | DBG("firstopen: dev=%p", dev); | ||
| 471 | return 0; | ||
| 472 | } | ||
| 473 | |||
| 474 | /** | ||
| 475 | * lastclose - clean up after all DRM clients have exited | ||
| 476 | * @dev: DRM device | ||
| 477 | * | ||
| 478 | * Take care of cleaning up after all DRM clients have exited. In the | ||
| 479 | * mode setting case, we want to restore the kernel's initial mode (just | ||
| 480 | * in case the last client left us in a bad state). | ||
| 481 | * | ||
| 482 | * Additionally, in the non-mode setting case, we'll tear down the AGP | ||
| 483 | * and DMA structures, since the kernel won't be using them, and clean | ||
| 484 | * up any GEM state. | ||
| 485 | */ | ||
| 486 | static void dev_lastclose(struct drm_device * dev) | ||
| 487 | { | ||
| 488 | DBG("lastclose: dev=%p", dev); | ||
| 489 | } | ||
| 490 | |||
| 491 | static void dev_preclose(struct drm_device * dev, struct drm_file *file) | ||
| 492 | { | ||
| 493 | DBG("preclose: dev=%p", dev); | ||
| 494 | } | ||
| 495 | |||
| 496 | static void dev_postclose(struct drm_device *dev, struct drm_file *file) | ||
| 497 | { | ||
| 498 | struct omap_drm_plugin *plugin; | ||
| 499 | int ret; | ||
| 500 | |||
| 501 | DBG("postclose: dev=%p, file=%p", dev, file); | ||
| 502 | |||
| 503 | list_for_each_entry(plugin, &plugin_list, list) { | ||
| 504 | ret = plugin->release(dev, file); | ||
| 505 | } | ||
| 506 | |||
| 507 | return; | ||
| 508 | } | ||
| 509 | |||
| 510 | /** | ||
| 511 | * enable_vblank - enable vblank interrupt events | ||
| 512 | * @dev: DRM device | ||
| 513 | * @crtc: which irq to enable | ||
| 514 | * | ||
| 515 | * Enable vblank interrupts for @crtc. If the device doesn't have | ||
| 516 | * a hardware vblank counter, this routine should be a no-op, since | ||
| 517 | * interrupts will have to stay on to keep the count accurate. | ||
| 518 | * | ||
| 519 | * RETURNS | ||
| 520 | * Zero on success, appropriate errno if the given @crtc's vblank | ||
| 521 | * interrupt cannot be enabled. | ||
| 522 | */ | ||
| 523 | static int dev_enable_vblank(struct drm_device *dev, int crtc) | ||
| 524 | { | ||
| 525 | DBG("enable_vblank: dev=%p, crtc=%d", dev, crtc); | ||
| 526 | return 0; | ||
| 527 | } | ||
| 528 | |||
| 529 | /** | ||
| 530 | * disable_vblank - disable vblank interrupt events | ||
| 531 | * @dev: DRM device | ||
| 532 | * @crtc: which irq to enable | ||
| 533 | * | ||
| 534 | * Disable vblank interrupts for @crtc. If the device doesn't have | ||
| 535 | * a hardware vblank counter, this routine should be a no-op, since | ||
| 536 | * interrupts will have to stay on to keep the count accurate. | ||
| 537 | */ | ||
| 538 | static void dev_disable_vblank(struct drm_device *dev, int crtc) | ||
| 539 | { | ||
| 540 | DBG("disable_vblank: dev=%p, crtc=%d", dev, crtc); | ||
| 541 | } | ||
| 542 | |||
| 543 | /** | ||
| 544 | * Called by \c drm_device_is_agp. Typically used to determine if a | ||
| 545 | * card is really attached to AGP or not. | ||
| 546 | * | ||
| 547 | * \param dev DRM device handle | ||
| 548 | * | ||
| 549 | * \returns | ||
| 550 | * One of three values is returned depending on whether or not the | ||
| 551 | * card is absolutely \b not AGP (return of 0), absolutely \b is AGP | ||
| 552 | * (return of 1), or may or may not be AGP (return of 2). | ||
| 553 | */ | ||
| 554 | static int dev_device_is_agp(struct drm_device *dev) | ||
| 555 | { | ||
| 556 | return 0; | ||
| 557 | } | ||
| 558 | |||
| 559 | static irqreturn_t dev_irq_handler(DRM_IRQ_ARGS) | ||
| 560 | { | ||
| 561 | return IRQ_HANDLED; | ||
| 562 | } | ||
| 563 | |||
| 564 | static void dev_irq_preinstall(struct drm_device *dev) | ||
| 565 | { | ||
| 566 | DBG("irq_preinstall: dev=%p", dev); | ||
| 567 | } | ||
| 568 | |||
| 569 | static int dev_irq_postinstall(struct drm_device *dev) | ||
| 570 | { | ||
| 571 | DBG("irq_postinstall: dev=%p", dev); | ||
| 572 | return 0; | ||
| 573 | } | ||
| 574 | |||
| 575 | static void dev_irq_uninstall(struct drm_device *dev) | ||
| 576 | { | ||
| 577 | DBG("irq_uninstall: dev=%p", dev); | ||
| 578 | } | ||
| 579 | |||
| 580 | static int fop_mmap(struct file *file, struct vm_area_struct *vma) | ||
| 581 | { | ||
| 582 | struct omap_drm_plugin *plugin; | ||
| 583 | int ret = 0; | ||
| 584 | |||
| 585 | list_for_each_entry(plugin, &plugin_list, list) { | ||
| 586 | ret = plugin->mmap(file, vma); | ||
| 587 | if (!ret) { | ||
| 588 | /* on first plugin that succeeds, bail out of iteration */ | ||
| 589 | return ret; | ||
| 590 | } | ||
| 591 | } | ||
| 592 | |||
| 593 | return ret; | ||
| 594 | } | ||
| 595 | |||
| 596 | struct drm_ioctl_desc ioctls[DRM_COMMAND_END - DRM_COMMAND_BASE] = {{0}}; | ||
| 597 | |||
| 598 | static struct drm_driver omap_drm_driver = { | ||
| 599 | .driver_features = DRIVER_HAVE_IRQ | DRIVER_MODESET, | ||
| 600 | .load = dev_load, | ||
| 601 | .unload = dev_unload, | ||
| 602 | .open = dev_open, | ||
| 603 | .firstopen = dev_firstopen, | ||
| 604 | .lastclose = dev_lastclose, | ||
| 605 | .preclose = dev_preclose, | ||
| 606 | .postclose = dev_postclose, | ||
| 607 | .get_vblank_counter = drm_vblank_count, | ||
| 608 | .enable_vblank = dev_enable_vblank, | ||
| 609 | .disable_vblank = dev_disable_vblank, | ||
| 610 | .device_is_agp = dev_device_is_agp, | ||
| 611 | .irq_preinstall = dev_irq_preinstall, | ||
| 612 | .irq_postinstall = dev_irq_postinstall, | ||
| 613 | .irq_uninstall = dev_irq_uninstall, | ||
| 614 | .irq_handler = dev_irq_handler, | ||
| 615 | .reclaim_buffers = drm_core_reclaim_buffers, | ||
| 616 | .ioctls = ioctls, | ||
| 617 | .num_ioctls = 0, | ||
| 618 | .fops = { | ||
| 619 | .owner = THIS_MODULE, | ||
| 620 | .open = drm_open, | ||
| 621 | .unlocked_ioctl = drm_ioctl, | ||
| 622 | .release = drm_release, | ||
| 623 | .mmap = fop_mmap, | ||
| 624 | .poll = drm_poll, | ||
| 625 | .fasync = drm_fasync, | ||
| 626 | .read = drm_read, | ||
| 627 | }, | ||
| 628 | .name = DRIVER_NAME, | ||
| 629 | .desc = DRIVER_DESC, | ||
| 630 | .date = DRIVER_DATE, | ||
| 631 | .major = DRIVER_MAJOR, | ||
| 632 | .minor = DRIVER_MINOR, | ||
| 633 | .patchlevel = DRIVER_PATCHLEVEL, | ||
| 634 | }; | ||
| 635 | |||
| 636 | int omap_drm_register_plugin(struct omap_drm_plugin *plugin) | ||
| 637 | { | ||
| 638 | struct drm_device *dev = drm_device; | ||
| 639 | int i; | ||
| 640 | |||
| 641 | DBG("register plugin: %p (%s)", plugin, plugin->name); | ||
| 642 | |||
| 643 | /* XXX: PVR code uses drm_file->driver_priv... | ||
| 644 | * need to come up with some sane way to handle this. | ||
| 645 | */ | ||
| 646 | |||
| 647 | list_add_tail(&plugin->list, &plugin_list); | ||
| 648 | |||
| 649 | /* register the plugin's ioctl's */ | ||
| 650 | for (i = 0; i < plugin->num_ioctls; i++) { | ||
| 651 | int nr = i + plugin->ioctl_start; | ||
| 652 | |||
| 653 | /* check for out of bounds ioctl nr or already registered ioctl */ | ||
| 654 | if (nr > ARRAY_SIZE(ioctls) || ioctls[nr].func) { | ||
| 655 | dev_err(dev->dev, "invalid ioctl: %d (nr=%d)\n", i, nr); | ||
| 656 | return -EINVAL; | ||
| 657 | } | ||
| 658 | |||
| 659 | DBG("register ioctl: %d %08x", nr, plugin->ioctls[i].cmd); | ||
| 660 | |||
| 661 | ioctls[nr] = plugin->ioctls[i]; | ||
| 662 | |||
| 663 | if (nr >= omap_drm_driver.num_ioctls) { | ||
| 664 | omap_drm_driver.num_ioctls = nr + 1; | ||
| 665 | } | ||
| 666 | } | ||
| 667 | |||
| 668 | if (loaded) { | ||
| 669 | plugin->load(dev, 0); | ||
| 670 | } | ||
| 671 | |||
| 672 | return 0; | ||
| 673 | } | ||
| 674 | EXPORT_SYMBOL(omap_drm_register_plugin); | ||
| 675 | |||
| 676 | int omap_drm_unregister_plugin(struct omap_drm_plugin *plugin) | ||
| 677 | { | ||
| 678 | list_del(&plugin->list); | ||
| 679 | return 0; | ||
| 680 | } | ||
| 681 | EXPORT_SYMBOL(omap_drm_unregister_plugin); | ||
| 682 | |||
| 683 | struct drm_framebuffer * omap_drm_get_default_fb(struct drm_device *dev) | ||
| 684 | { | ||
| 685 | struct omap_drm_private *priv = dev->dev_private; | ||
| 686 | return priv->fbdev->fb; | ||
| 687 | } | ||
| 688 | EXPORT_SYMBOL(omap_drm_get_default_fb); | ||
| 689 | |||
| 690 | static int pdev_suspend(struct platform_device *pDevice, pm_message_t state) | ||
| 691 | { | ||
| 692 | DBG(""); | ||
| 693 | return 0; | ||
| 694 | } | ||
| 695 | |||
| 696 | static int pdev_resume(struct platform_device *device) | ||
| 697 | { | ||
| 698 | DBG(""); | ||
| 699 | return 0; | ||
| 700 | } | ||
| 701 | |||
| 702 | static void pdev_shutdown(struct platform_device *device) | ||
| 703 | { | ||
| 704 | DBG(""); | ||
| 705 | } | ||
| 706 | |||
| 707 | static int pdev_probe(struct platform_device *device) | ||
| 708 | { | ||
| 709 | DBG("%s", device->name); | ||
| 710 | return drm_platform_init(&omap_drm_driver, device); | ||
| 711 | } | ||
| 712 | |||
| 713 | static int pdev_remove(struct platform_device *device) | ||
| 714 | { | ||
| 715 | DBG(""); | ||
| 716 | drm_platform_exit(&omap_drm_driver, device); | ||
| 717 | return 0; | ||
| 718 | } | ||
| 719 | |||
| 720 | struct platform_driver pdev = { | ||
| 721 | .driver = { | ||
| 722 | .name = DRIVER_NAME, | ||
| 723 | .owner = THIS_MODULE, | ||
| 724 | }, | ||
| 725 | .probe = pdev_probe, | ||
| 726 | .remove = pdev_remove, | ||
| 727 | .suspend = pdev_suspend, | ||
| 728 | .resume = pdev_resume, | ||
| 729 | .shutdown = pdev_shutdown, | ||
| 730 | }; | ||
| 731 | |||
| 732 | static int __init omap_drm_init(void) | ||
| 733 | { | ||
| 734 | DBG("init"); | ||
| 735 | return platform_driver_register(&pdev); | ||
| 736 | } | ||
| 737 | |||
| 738 | static void __exit omap_drm_fini(void) | ||
| 739 | { | ||
| 740 | DBG("fini"); | ||
| 741 | platform_driver_unregister(&pdev); | ||
| 742 | } | ||
| 743 | |||
| 744 | /* need late_initcall() so we load after dss_driver's are loaded */ | ||
| 745 | late_initcall(omap_drm_init); | ||
| 746 | module_exit(omap_drm_fini); | ||
| 747 | |||
| 748 | MODULE_AUTHOR("Rob Clark <rob@ti.com>"); | ||
| 749 | MODULE_DESCRIPTION("OMAP DRM Display Driver"); | ||
| 750 | MODULE_ALIAS("platform:" DRIVER_NAME); | ||
| 751 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/drivers/staging/omapdrm/omap_drv.h b/drivers/staging/omapdrm/omap_drv.h new file mode 100644 index 00000000000..3a64d5254ea --- /dev/null +++ b/drivers/staging/omapdrm/omap_drv.h | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/staging/omapdrm/omap_drv.h | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 Texas Instruments | ||
| 5 | * Author: Rob Clark <rob@ti.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify it | ||
| 8 | * under the terms of the GNU General Public License version 2 as published by | ||
| 9 | * the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 14 | * more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License along with | ||
| 17 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #ifndef __OMAP_DRV_H__ | ||
| 21 | #define __OMAP_DRV_H__ | ||
| 22 | |||
| 23 | #include <video/omapdss.h> | ||
| 24 | #include <linux/module.h> | ||
| 25 | |||
| 26 | #define DBG(fmt,...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__) | ||
| 27 | #define VERB(fmt,...) if (0) DRM_DEBUG(fmt, ##__VA_ARGS__) /* verbose debug */ | ||
| 28 | |||
| 29 | #define MODULE_NAME "omapdrm" | ||
| 30 | |||
| 31 | struct omap_drm_private { | ||
| 32 | int num_crtcs; | ||
| 33 | struct drm_crtc *crtcs[8]; | ||
| 34 | int num_encoders; | ||
| 35 | struct drm_encoder *encoders[8]; | ||
| 36 | int num_connectors; | ||
| 37 | struct drm_connector *connectors[8]; | ||
| 38 | |||
| 39 | struct drm_fb_helper *fbdev; | ||
| 40 | }; | ||
| 41 | |||
| 42 | struct drm_fb_helper * omap_fbdev_init(struct drm_device *dev); | ||
| 43 | |||
| 44 | struct drm_crtc * omap_crtc_init(struct drm_device *dev, | ||
| 45 | struct omap_overlay *ovl, int id); | ||
| 46 | struct omap_overlay * omap_crtc_get_overlay(struct drm_crtc *crtc); | ||
| 47 | |||
| 48 | struct drm_encoder * omap_encoder_init(struct drm_device *dev, | ||
| 49 | struct omap_overlay_manager *mgr); | ||
| 50 | struct omap_overlay_manager * omap_encoder_get_manager( | ||
| 51 | struct drm_encoder *encoder); | ||
| 52 | struct drm_encoder * omap_connector_attached_encoder ( | ||
| 53 | struct drm_connector *connector); | ||
| 54 | enum drm_connector_status omap_connector_detect( | ||
| 55 | struct drm_connector *connector, bool force); | ||
| 56 | |||
| 57 | struct drm_connector * omap_connector_init(struct drm_device *dev, | ||
| 58 | int connector_type, struct omap_dss_device *dssdev); | ||
| 59 | void omap_connector_mode_set(struct drm_connector *connector, | ||
| 60 | struct drm_display_mode *mode); | ||
| 61 | void omap_connector_flush(struct drm_connector *connector, | ||
| 62 | int x, int y, int w, int h); | ||
| 63 | void omap_connector_dpms(struct drm_connector *connector, int mode); | ||
| 64 | |||
| 65 | struct drm_framebuffer * omap_framebuffer_create(struct drm_device *dev, | ||
| 66 | struct drm_file *file, struct drm_mode_fb_cmd *mode_cmd); | ||
| 67 | |||
| 68 | #endif /* __OMAP_DRV_H__ */ | ||
diff --git a/drivers/staging/omapdrm/omap_encoder.c b/drivers/staging/omapdrm/omap_encoder.c new file mode 100644 index 00000000000..810c7e244fa --- /dev/null +++ b/drivers/staging/omapdrm/omap_encoder.c | |||
| @@ -0,0 +1,198 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/staging/omapdrm/omap_encoder.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 Texas Instruments | ||
| 5 | * Author: Rob Clark <rob@ti.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify it | ||
| 8 | * under the terms of the GNU General Public License version 2 as published by | ||
| 9 | * the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 14 | * more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License along with | ||
| 17 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #include <linux/omap_drm.h> | ||
| 21 | #include "omap_drv.h" | ||
| 22 | |||
| 23 | #include "drm_crtc.h" | ||
| 24 | #include "drm_crtc_helper.h" | ||
| 25 | |||
| 26 | /* | ||
| 27 | * encoder funcs | ||
| 28 | */ | ||
| 29 | |||
| 30 | #define to_omap_encoder(x) container_of(x, struct omap_encoder, base) | ||
| 31 | |||
| 32 | struct omap_encoder { | ||
| 33 | struct drm_encoder base; | ||
| 34 | struct omap_overlay_manager *mgr; | ||
| 35 | }; | ||
| 36 | |||
| 37 | static void omap_encoder_destroy(struct drm_encoder *encoder) | ||
| 38 | { | ||
| 39 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); | ||
| 40 | DBG("%s", omap_encoder->mgr->name); | ||
| 41 | drm_encoder_cleanup(encoder); | ||
| 42 | kfree(omap_encoder); | ||
| 43 | } | ||
| 44 | |||
| 45 | static void omap_encoder_dpms(struct drm_encoder *encoder, int mode) | ||
| 46 | { | ||
| 47 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); | ||
| 48 | struct drm_device *dev = encoder->dev; | ||
| 49 | struct omap_drm_private *priv = dev->dev_private; | ||
| 50 | int i; | ||
| 51 | |||
| 52 | DBG("%s: %d", omap_encoder->mgr->name, mode); | ||
| 53 | |||
| 54 | /* managers don't need to do anything for DPMS.. but we do | ||
| 55 | * need to propagate to the connector, who is actually going | ||
| 56 | * to enable/disable as needed: | ||
| 57 | */ | ||
| 58 | for (i = 0; i < priv->num_connectors; i++) { | ||
| 59 | struct drm_connector *connector = priv->connectors[i]; | ||
| 60 | if (connector->encoder == encoder) { | ||
| 61 | omap_connector_dpms(connector, mode); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | static bool omap_encoder_mode_fixup(struct drm_encoder *encoder, | ||
| 67 | struct drm_display_mode *mode, | ||
| 68 | struct drm_display_mode *adjusted_mode) | ||
| 69 | { | ||
| 70 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); | ||
| 71 | DBG("%s", omap_encoder->mgr->name); | ||
| 72 | return true; | ||
| 73 | } | ||
| 74 | |||
| 75 | static void omap_encoder_mode_set(struct drm_encoder *encoder, | ||
| 76 | struct drm_display_mode *mode, | ||
| 77 | struct drm_display_mode *adjusted_mode) | ||
| 78 | { | ||
| 79 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); | ||
| 80 | struct drm_device *dev = encoder->dev; | ||
| 81 | struct omap_drm_private *priv = dev->dev_private; | ||
| 82 | int i; | ||
| 83 | |||
| 84 | mode = adjusted_mode; | ||
| 85 | |||
| 86 | DBG("%s: set mode: %dx%d", omap_encoder->mgr->name, | ||
| 87 | mode->hdisplay, mode->vdisplay); | ||
| 88 | |||
| 89 | for (i = 0; i < priv->num_connectors; i++) { | ||
| 90 | struct drm_connector *connector = priv->connectors[i]; | ||
| 91 | if (connector->encoder == encoder) { | ||
| 92 | omap_connector_mode_set(connector, mode); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | static void omap_encoder_prepare(struct drm_encoder *encoder) | ||
| 98 | { | ||
| 99 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); | ||
| 100 | struct drm_encoder_helper_funcs *encoder_funcs = | ||
| 101 | encoder->helper_private; | ||
| 102 | DBG("%s", omap_encoder->mgr->name); | ||
| 103 | encoder_funcs->dpms(encoder, DRM_MODE_DPMS_OFF); | ||
| 104 | } | ||
| 105 | |||
| 106 | static void omap_encoder_commit(struct drm_encoder *encoder) | ||
| 107 | { | ||
| 108 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); | ||
| 109 | struct drm_encoder_helper_funcs *encoder_funcs = | ||
| 110 | encoder->helper_private; | ||
| 111 | DBG("%s", omap_encoder->mgr->name); | ||
| 112 | omap_encoder->mgr->apply(omap_encoder->mgr); | ||
| 113 | encoder_funcs->dpms(encoder, DRM_MODE_DPMS_ON); | ||
| 114 | } | ||
| 115 | |||
| 116 | static const struct drm_encoder_funcs omap_encoder_funcs = { | ||
| 117 | .destroy = omap_encoder_destroy, | ||
| 118 | }; | ||
| 119 | |||
| 120 | static const struct drm_encoder_helper_funcs omap_encoder_helper_funcs = { | ||
| 121 | .dpms = omap_encoder_dpms, | ||
| 122 | .mode_fixup = omap_encoder_mode_fixup, | ||
| 123 | .mode_set = omap_encoder_mode_set, | ||
| 124 | .prepare = omap_encoder_prepare, | ||
| 125 | .commit = omap_encoder_commit, | ||
| 126 | }; | ||
| 127 | |||
| 128 | struct omap_overlay_manager * omap_encoder_get_manager( | ||
| 129 | struct drm_encoder *encoder) | ||
| 130 | { | ||
| 131 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); | ||
| 132 | return omap_encoder->mgr; | ||
| 133 | } | ||
| 134 | |||
| 135 | /* maybe this could go away and we just use drm_vblank_wait()? */ | ||
| 136 | int omap_encoder_wait_for_vsync(struct drm_encoder *encoder) | ||
| 137 | { | ||
| 138 | struct omap_encoder *omap_encoder = to_omap_encoder(encoder); | ||
| 139 | DBG("%s", omap_encoder->mgr->name); | ||
| 140 | return omap_encoder->mgr->wait_for_vsync(omap_encoder->mgr); | ||
| 141 | } | ||
| 142 | EXPORT_SYMBOL(omap_encoder_wait_for_vsync); | ||
| 143 | |||
| 144 | /* initialize encoder */ | ||
| 145 | struct drm_encoder * omap_encoder_init(struct drm_device *dev, | ||
| 146 | struct omap_overlay_manager *mgr) | ||
| 147 | { | ||
| 148 | struct drm_encoder *encoder = NULL; | ||
| 149 | struct omap_encoder *omap_encoder; | ||
| 150 | struct omap_overlay_manager_info info; | ||
| 151 | int ret; | ||
| 152 | |||
| 153 | DBG("%s", mgr->name); | ||
| 154 | |||
| 155 | omap_encoder = kzalloc(sizeof(*omap_encoder), GFP_KERNEL); | ||
| 156 | if (!omap_encoder) { | ||
| 157 | dev_err(dev->dev, "could not allocate encoder\n"); | ||
| 158 | goto fail; | ||
| 159 | } | ||
| 160 | |||
| 161 | omap_encoder->mgr = mgr; | ||
| 162 | encoder = &omap_encoder->base; | ||
| 163 | |||
| 164 | drm_encoder_init(dev, encoder, &omap_encoder_funcs, | ||
| 165 | DRM_MODE_ENCODER_TMDS); | ||
| 166 | drm_encoder_helper_add(encoder, &omap_encoder_helper_funcs); | ||
| 167 | |||
| 168 | mgr->get_manager_info(mgr, &info); | ||
| 169 | |||
| 170 | /* TODO: fix hard-coded setup.. */ | ||
| 171 | info.default_color = 0x00000000; | ||
| 172 | info.trans_key = 0x00000000; | ||
| 173 | info.trans_key_type = OMAP_DSS_COLOR_KEY_GFX_DST; | ||
| 174 | info.trans_enabled = false; | ||
| 175 | info.alpha_enabled = true; | ||
| 176 | |||
| 177 | ret = mgr->set_manager_info(mgr, &info); | ||
| 178 | if (ret) { | ||
| 179 | dev_err(dev->dev, "could not set manager info\n"); | ||
| 180 | goto fail; | ||
| 181 | } | ||
| 182 | |||
| 183 | ret = mgr->apply(mgr); | ||
| 184 | if (ret) { | ||
| 185 | dev_err(dev->dev, "could not apply\n"); | ||
| 186 | goto fail; | ||
| 187 | } | ||
| 188 | |||
| 189 | return encoder; | ||
| 190 | |||
| 191 | fail: | ||
| 192 | if (encoder) { | ||
| 193 | drm_encoder_cleanup(encoder); | ||
| 194 | kfree(omap_encoder); | ||
| 195 | } | ||
| 196 | |||
| 197 | return NULL; | ||
| 198 | } | ||
diff --git a/drivers/staging/omapdrm/omap_fb.c b/drivers/staging/omapdrm/omap_fb.c new file mode 100644 index 00000000000..c93ec2e9e69 --- /dev/null +++ b/drivers/staging/omapdrm/omap_fb.c | |||
| @@ -0,0 +1,251 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/staging/omapdrm/omap_fb.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 Texas Instruments | ||
| 5 | * Author: Rob Clark <rob@ti.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify it | ||
| 8 | * under the terms of the GNU General Public License version 2 as published by | ||
| 9 | * the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 14 | * more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License along with | ||
| 17 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #include <plat/vram.h> | ||
| 21 | |||
| 22 | #include <linux/omap_drm.h> | ||
| 23 | #include "omap_drv.h" | ||
| 24 | |||
| 25 | #include "drm_crtc.h" | ||
| 26 | #include "drm_crtc_helper.h" | ||
| 27 | |||
| 28 | |||
| 29 | static char *def_vram; | ||
| 30 | module_param_named(vram, def_vram, charp, 0); | ||
| 31 | |||
| 32 | /* | ||
| 33 | * framebuffer funcs | ||
| 34 | */ | ||
| 35 | |||
| 36 | #define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) | ||
| 37 | |||
| 38 | struct omap_framebuffer { | ||
| 39 | struct drm_framebuffer base; | ||
| 40 | |||
| 41 | /* framebuffer size/phys-addr/virt-addr */ | ||
| 42 | int size; | ||
| 43 | unsigned long paddr; | ||
| 44 | void __iomem *vaddr; | ||
| 45 | }; | ||
| 46 | |||
| 47 | static int omap_framebuffer_create_handle(struct drm_framebuffer *fb, | ||
| 48 | struct drm_file *file_priv, | ||
| 49 | unsigned int *handle) | ||
| 50 | { | ||
| 51 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); | ||
| 52 | DBG("framebuffer: get handle: %p", omap_fb); | ||
| 53 | |||
| 54 | // TODO, I suppose this really should be some sort of GEM handle | ||
| 55 | // to the framebuffer object, in case it needs to be mapped or | ||
| 56 | // something. Right now this will go-exist badly with PVR, who | ||
| 57 | // implements the mmap() fxn.. need to think about how to handle | ||
| 58 | // this.. | ||
| 59 | |||
| 60 | *handle = 42; | ||
| 61 | |||
| 62 | return 0; | ||
| 63 | } | ||
| 64 | |||
| 65 | static void omap_framebuffer_destroy(struct drm_framebuffer *fb) | ||
| 66 | { | ||
| 67 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); | ||
| 68 | |||
| 69 | DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); | ||
| 70 | |||
| 71 | drm_framebuffer_cleanup(fb); | ||
| 72 | |||
| 73 | if (omap_fb->vaddr) { | ||
| 74 | iounmap(omap_fb->vaddr); | ||
| 75 | } | ||
| 76 | |||
| 77 | /* drm framework should, but doesn't (as of 2.6.35) disconnect any | ||
| 78 | * CRTCs connected to this fb before destroying it.. so could be | ||
| 79 | * some small window when garbage is seen on screen. But in | ||
| 80 | * practice, unlikely because we have a private vram pool. So I | ||
| 81 | * won't worry too much about it. | ||
| 82 | */ | ||
| 83 | if (omap_fb->paddr) { | ||
| 84 | omap_vram_free(omap_fb->paddr, omap_fb->size); | ||
| 85 | } | ||
| 86 | |||
| 87 | kfree(omap_fb); | ||
| 88 | } | ||
| 89 | |||
| 90 | static int omap_framebuffer_dirty(struct drm_framebuffer *fb, | ||
| 91 | struct drm_file *file_priv, unsigned flags, unsigned color, | ||
| 92 | struct drm_clip_rect *clips, unsigned num_clips) | ||
| 93 | { | ||
| 94 | int i; | ||
| 95 | |||
| 96 | for (i = 0; i < num_clips; i++) { | ||
| 97 | omap_framebuffer_flush(fb, clips[i].x1, clips[i].y1, | ||
| 98 | clips[i].x2 - clips[i].x1, | ||
| 99 | clips[i].y2 - clips[i].y1); | ||
| 100 | } | ||
| 101 | |||
| 102 | return 0; | ||
| 103 | } | ||
| 104 | |||
| 105 | static const struct drm_framebuffer_funcs omap_framebuffer_funcs = { | ||
| 106 | .create_handle = omap_framebuffer_create_handle, | ||
| 107 | .destroy = omap_framebuffer_destroy, | ||
| 108 | .dirty = omap_framebuffer_dirty, | ||
| 109 | }; | ||
| 110 | |||
| 111 | int omap_framebuffer_get_buffer(struct drm_framebuffer *fb, int x, int y, | ||
| 112 | void **vaddr, unsigned long *paddr, int *screen_width) | ||
| 113 | { | ||
| 114 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); | ||
| 115 | int bpp = 4; //XXX fb->depth / 8; | ||
| 116 | unsigned long offset; | ||
| 117 | |||
| 118 | offset = (x * bpp) + (y * fb->pitch); | ||
| 119 | |||
| 120 | *vaddr = omap_fb->vaddr + offset; | ||
| 121 | *paddr = omap_fb->paddr + offset; | ||
| 122 | *screen_width = fb->pitch / bpp; | ||
| 123 | |||
| 124 | return omap_fb->size; | ||
| 125 | } | ||
| 126 | EXPORT_SYMBOL(omap_framebuffer_get_buffer); | ||
| 127 | |||
| 128 | /* iterate thru all the connectors, returning ones that are attached | ||
| 129 | * to the same fb.. | ||
| 130 | */ | ||
| 131 | struct drm_connector * omap_framebuffer_get_next_connector( | ||
| 132 | struct drm_framebuffer *fb, struct drm_connector *from) | ||
| 133 | { | ||
| 134 | struct drm_device *dev = fb->dev; | ||
| 135 | struct list_head *connector_list = &dev->mode_config.connector_list; | ||
| 136 | struct drm_connector *connector = from; | ||
| 137 | |||
| 138 | if (!from) { | ||
| 139 | return list_first_entry(connector_list, typeof(*from), head); | ||
| 140 | } | ||
| 141 | |||
| 142 | list_for_each_entry_from(connector, connector_list, head) { | ||
| 143 | if (connector != from) { | ||
| 144 | struct drm_encoder *encoder = connector->encoder; | ||
| 145 | struct drm_crtc *crtc = encoder ? encoder->crtc : NULL; | ||
| 146 | if (crtc && crtc->fb == fb) { | ||
| 147 | return connector; | ||
| 148 | } | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | return NULL; | ||
| 153 | } | ||
| 154 | EXPORT_SYMBOL(omap_framebuffer_get_next_connector); | ||
| 155 | |||
| 156 | /* flush an area of the framebuffer (in case of manual update display that | ||
| 157 | * is not automatically flushed) | ||
| 158 | */ | ||
| 159 | void omap_framebuffer_flush(struct drm_framebuffer *fb, | ||
| 160 | int x, int y, int w, int h) | ||
| 161 | { | ||
| 162 | struct drm_connector *connector = NULL; | ||
| 163 | |||
| 164 | VERB("flush: %d,%d %dx%d, fb=%p", x, y, w, h, fb); | ||
| 165 | |||
| 166 | while ((connector = omap_framebuffer_get_next_connector(fb, connector))) { | ||
| 167 | /* only consider connectors that are part of a chain */ | ||
| 168 | if (connector->encoder && connector->encoder->crtc) { | ||
| 169 | /* TODO: maybe this should propagate thru the crtc who | ||
| 170 | * could do the coordinate translation.. | ||
| 171 | */ | ||
| 172 | struct drm_crtc *crtc = connector->encoder->crtc; | ||
| 173 | int cx = max(0, x - crtc->x); | ||
| 174 | int cy = max(0, y - crtc->y); | ||
| 175 | int cw = w + (x - crtc->x) - cx; | ||
| 176 | int ch = h + (y - crtc->y) - cy; | ||
| 177 | |||
| 178 | omap_connector_flush(connector, cx, cy, cw, ch); | ||
| 179 | } | ||
| 180 | } | ||
| 181 | } | ||
| 182 | EXPORT_SYMBOL(omap_framebuffer_flush); | ||
| 183 | |||
| 184 | struct drm_framebuffer * omap_framebuffer_create(struct drm_device *dev, | ||
| 185 | struct drm_file *file, struct drm_mode_fb_cmd *mode_cmd) | ||
| 186 | { | ||
| 187 | return omap_framebuffer_init(dev, mode_cmd); | ||
| 188 | } | ||
| 189 | |||
| 190 | struct drm_framebuffer * omap_framebuffer_init(struct drm_device *dev, | ||
| 191 | struct drm_mode_fb_cmd *mode_cmd) | ||
| 192 | { | ||
| 193 | struct omap_framebuffer *omap_fb; | ||
| 194 | struct drm_framebuffer *fb = NULL; | ||
| 195 | unsigned long paddr; | ||
| 196 | int size, ret; | ||
| 197 | |||
| 198 | /* in case someone tries to feed us a completely bogus stride: */ | ||
| 199 | mode_cmd->pitch = max(mode_cmd->pitch, | ||
| 200 | mode_cmd->width * mode_cmd->bpp / 8); | ||
| 201 | |||
| 202 | /* pvr needs to have a stride that is a multiple of 8 pixels: */ | ||
| 203 | mode_cmd->pitch = ALIGN(mode_cmd->pitch, 8 * (mode_cmd->bpp / 8)); | ||
| 204 | |||
| 205 | DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d)", dev, | ||
| 206 | mode_cmd, mode_cmd->width, mode_cmd->height); | ||
| 207 | |||
| 208 | omap_fb = kzalloc(sizeof(*omap_fb), GFP_KERNEL); | ||
| 209 | if (!omap_fb) { | ||
| 210 | dev_err(dev->dev, "could not allocate fb\n"); | ||
| 211 | goto fail; | ||
| 212 | } | ||
| 213 | |||
| 214 | fb = &omap_fb->base; | ||
| 215 | ret = drm_framebuffer_init(dev, fb, &omap_framebuffer_funcs); | ||
| 216 | if (ret) { | ||
| 217 | dev_err(dev->dev, "framebuffer init failed: %d\n", ret); | ||
| 218 | goto fail; | ||
| 219 | } | ||
| 220 | |||
| 221 | DBG("create: FB ID: %d (%p)", fb->base.id, fb); | ||
| 222 | |||
| 223 | size = PAGE_ALIGN(mode_cmd->pitch * mode_cmd->height); | ||
| 224 | |||
| 225 | DBG("allocating %d bytes for fb %d", size, dev->primary->index); | ||
| 226 | ret = omap_vram_alloc(OMAP_VRAM_MEMTYPE_SDRAM, size, &paddr); | ||
| 227 | if (ret) { | ||
| 228 | dev_err(dev->dev, "failed to allocate vram\n"); | ||
| 229 | goto fail; | ||
| 230 | } | ||
| 231 | |||
| 232 | omap_fb->size = size; | ||
| 233 | omap_fb->paddr = paddr; | ||
| 234 | omap_fb->vaddr = ioremap_wc(paddr, size); | ||
| 235 | |||
| 236 | if (!omap_fb->vaddr) { | ||
| 237 | dev_err(dev->dev, "failed to ioremap framebuffer\n"); | ||
| 238 | goto fail; | ||
| 239 | } | ||
| 240 | |||
| 241 | drm_helper_mode_fill_fb_struct(fb, mode_cmd); | ||
| 242 | |||
| 243 | return fb; | ||
| 244 | |||
| 245 | fail: | ||
| 246 | if (fb) { | ||
| 247 | omap_framebuffer_destroy(fb); | ||
| 248 | } | ||
| 249 | return NULL; | ||
| 250 | } | ||
| 251 | EXPORT_SYMBOL(omap_framebuffer_init); | ||
diff --git a/drivers/staging/omapdrm/omap_fbdev.c b/drivers/staging/omapdrm/omap_fbdev.c new file mode 100644 index 00000000000..205bc32b914 --- /dev/null +++ b/drivers/staging/omapdrm/omap_fbdev.c | |||
| @@ -0,0 +1,279 @@ | |||
| 1 | /* | ||
| 2 | * linux/drivers/staging/omapdrm/omap_fbdev.c | ||
| 3 | * | ||
| 4 | * Copyright (C) 2011 Texas Instruments | ||
| 5 | * Author: Rob Clark <rob@ti.com> | ||
| 6 | * | ||
| 7 | * This program is free software; you can redistribute it and/or modify it | ||
| 8 | * under the terms of the GNU General Public License version 2 as published by | ||
| 9 | * the Free Software Foundation. | ||
| 10 | * | ||
| 11 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 14 | * more details. | ||
| 15 | * | ||
| 16 | * You should have received a copy of the GNU General Public License along with | ||
| 17 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 18 | */ | ||
| 19 | |||
| 20 | #include <linux/omap_drm.h> | ||
| 21 | #include "omap_drv.h" | ||
| 22 | |||
| 23 | #include "drm_crtc.h" | ||
| 24 | #include "drm_fb_helper.h" | ||
| 25 | |||
| 26 | /* | ||
| 27 | * fbdev funcs, to implement legacy fbdev interface on top of drm driver | ||
| 28 | */ | ||
| 29 | |||
| 30 | #define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base) | ||
| 31 | |||
| 32 | struct omap_fbdev { | ||
| 33 | struct drm_fb_helper base; | ||
| 34 | struct drm_framebuffer *fb; | ||
| 35 | }; | ||
| 36 | |||
| 37 | static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h); | ||
| 38 | |||
| 39 | static ssize_t omap_fbdev_write(struct fb_info *fbi, const char __user *buf, | ||
| 40 | size_t count, loff_t *ppos) | ||
| 41 | { | ||
| 42 | ssize_t res; | ||
| 43 | |||
| 44 | res = fb_sys_write(fbi, buf, count, ppos); | ||
| 45 | omap_fbdev_flush(fbi, 0, 0, fbi->var.xres, fbi->var.yres); | ||
| 46 | |||
| 47 | return res; | ||
| 48 | } | ||
| 49 | |||
| 50 | static void omap_fbdev_fillrect(struct fb_info *fbi, | ||
| 51 | const struct fb_fillrect *rect) | ||
| 52 | { | ||
| 53 | sys_fillrect(fbi, rect); | ||
| 54 | omap_fbdev_flush(fbi, rect->dx, rect->dy, rect->width, rect->height); | ||
| 55 | } | ||
| 56 | |||
| 57 | static void omap_fbdev_copyarea(struct fb_info *fbi, | ||
| 58 | const struct fb_copyarea *area) | ||
| 59 | { | ||
| 60 | sys_copyarea(fbi, area); | ||
| 61 | omap_fbdev_flush(fbi, area->dx, area->dy, area->width, area->height); | ||
| 62 | } | ||
| 63 | |||
| 64 | static void omap_fbdev_imageblit(struct fb_info *fbi, | ||
| 65 | const struct fb_image *image) | ||
| 66 | { | ||
| 67 | sys_imageblit(fbi, image); | ||
| 68 | omap_fbdev_flush(fbi, image->dx, image->dy, | ||
| 69 | image->width, image->height); | ||
| 70 | } | ||
| 71 | |||
| 72 | static struct fb_ops omap_fb_ops = { | ||
| 73 | .owner = THIS_MODULE, | ||
| 74 | |||
| 75 | /* Note: to properly handle manual update displays, we wrap the | ||
| 76 | * basic fbdev ops which write to the framebuffer | ||
| 77 | */ | ||
| 78 | .fb_read = fb_sys_read, | ||
| 79 | .fb_write = omap_fbdev_write, | ||
| 80 | .fb_fillrect = omap_fbdev_fillrect, | ||
| 81 | .fb_copyarea = omap_fbdev_copyarea, | ||
| 82 | .fb_imageblit = omap_fbdev_imageblit, | ||
| 83 | |||
| 84 | .fb_check_var = drm_fb_helper_check_var, | ||
| 85 | .fb_set_par = drm_fb_helper_set_par, | ||
| 86 | .fb_pan_display = drm_fb_helper_pan_display, | ||
| 87 | .fb_blank = drm_fb_helper_blank, | ||
| 88 | .fb_setcmap = drm_fb_helper_setcmap, | ||
| 89 | |||
| 90 | .fb_debug_enter = drm_fb_helper_debug_enter, | ||
| 91 | .fb_debug_leave = drm_fb_helper_debug_leave, | ||
| 92 | }; | ||
| 93 | |||
| 94 | static int omap_fbdev_create(struct drm_fb_helper *helper, | ||
| 95 | struct drm_fb_helper_surface_size *sizes) | ||
| 96 | { | ||
| 97 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); | ||
| 98 | struct drm_device *dev = helper->dev; | ||
| 99 | struct drm_framebuffer *fb; | ||
| 100 | struct fb_info *fbi; | ||
| 101 | struct drm_mode_fb_cmd mode_cmd = {0}; | ||
| 102 | unsigned long paddr; | ||
| 103 | void __iomem *vaddr; | ||
| 104 | int size, screen_width; | ||
| 105 | int ret; | ||
| 106 | |||
| 107 | /* only doing ARGB32 since this is what is needed to alpha-blend | ||
| 108 | * with video overlays: | ||
| 109 | */ | ||
| 110 | sizes->surface_bpp = 32; | ||
| 111 | sizes->surface_depth = 32; | ||
| 112 | |||
| 113 | DBG("create fbdev: %dx%d@%d", sizes->surface_width, | ||
| 114 | sizes->surface_height, sizes->surface_bpp); | ||
| 115 | |||
| 116 | mode_cmd.width = sizes->surface_width; | ||
| 117 | mode_cmd.height = sizes->surface_height; | ||
| 118 | |||
| 119 | mode_cmd.bpp = sizes->surface_bpp; | ||
| 120 | mode_cmd.depth = sizes->surface_depth; | ||
| 121 | |||
| 122 | mutex_lock(&dev->struct_mutex); | ||
| 123 | |||
| 124 | fbi = framebuffer_alloc(0, dev->dev); | ||
| 125 | if (!fbi) { | ||
| 126 | dev_err(dev->dev, "failed to allocate fb info\n"); | ||
| 127 | ret = -ENOMEM; | ||
| 128 | goto fail; | ||
| 129 | } | ||
| 130 | |||
| 131 | DBG("fbi=%p, dev=%p", fbi, dev); | ||
| 132 | |||
| 133 | fbdev->fb = omap_framebuffer_init(dev, &mode_cmd); | ||
| 134 | if (!fbdev->fb) { | ||
| 135 | dev_err(dev->dev, "failed to allocate fb\n"); | ||
| 136 | ret = -ENOMEM; | ||
| 137 | goto fail; | ||
| 138 | } | ||
| 139 | |||
| 140 | fb = fbdev->fb; | ||
| 141 | helper->fb = fb; | ||
| 142 | helper->fbdev = fbi; | ||
| 143 | |||
| 144 | fbi->par = helper; | ||
| 145 | fbi->flags = FBINFO_DEFAULT; | ||
| 146 | fbi->fbops = &omap_fb_ops; | ||
| 147 | |||
| 148 | strcpy(fbi->fix.id, MODULE_NAME); | ||
| 149 | |||
| 150 | ret = fb_alloc_cmap(&fbi->cmap, 256, 0); | ||
| 151 | if (ret) { | ||
| 152 | ret = -ENOMEM; | ||
| 153 | goto fail; | ||
| 154 | } | ||
| 155 | |||
| 156 | drm_fb_helper_fill_fix(fbi, fb->pitch, fb->depth); | ||
| 157 | drm_fb_helper_fill_var(fbi, helper, fb->width, fb->height); | ||
| 158 | |||
| 159 | size = omap_framebuffer_get_buffer(fb, 0, 0, | ||
| 160 | &vaddr, &paddr, &screen_width); | ||
| 161 | |||
| 162 | dev->mode_config.fb_base = paddr; | ||
| 163 | |||
| 164 | fbi->screen_base = vaddr; | ||
| 165 | fbi->screen_size = size; | ||
| 166 | fbi->fix.smem_start = paddr; | ||
| 167 | fbi->fix.smem_len = size; | ||
| 168 | |||
| 169 | DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); | ||
| 170 | DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); | ||
| 171 | |||
| 172 | mutex_unlock(&dev->struct_mutex); | ||
| 173 | |||
| 174 | return 0; | ||
| 175 | |||
| 176 | fail: | ||
| 177 | mutex_unlock(&dev->struct_mutex); | ||
| 178 | // TODO cleanup? | ||
| 179 | return ret; | ||
| 180 | } | ||
| 181 | |||
| 182 | static void omap_crtc_fb_gamma_set(struct drm_crtc *crtc, | ||
| 183 | u16 red, u16 green, u16 blue, int regno) | ||
| 184 | { | ||
| 185 | DBG("fbdev: set gamma"); | ||
| 186 | // XXX ignore? | ||
| 187 | } | ||
| 188 | |||
| 189 | static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc, | ||
| 190 | u16 *red, u16 *green, u16 *blue, int regno) | ||
| 191 | { | ||
| 192 | DBG("fbdev: get gamma"); | ||
| 193 | // XXX ignore? | ||
| 194 | } | ||
| 195 | |||
| 196 | static int omap_fbdev_probe(struct drm_fb_helper *helper, | ||
| 197 | struct drm_fb_helper_surface_size *sizes) | ||
| 198 | { | ||
| 199 | int new_fb = 0; | ||
| 200 | int ret; | ||
| 201 | |||
| 202 | if (!helper->fb) { | ||
| 203 | ret = omap_fbdev_create(helper, sizes); | ||
| 204 | if (ret) | ||
| 205 | return ret; | ||
| 206 | new_fb = 1; | ||
| 207 | } | ||
| 208 | return new_fb; | ||
| 209 | } | ||
| 210 | |||
| 211 | static struct drm_fb_helper_funcs omap_fb_helper_funcs = { | ||
| 212 | .gamma_set = omap_crtc_fb_gamma_set, | ||
| 213 | .gamma_get = omap_crtc_fb_gamma_get, | ||
| 214 | .fb_probe = omap_fbdev_probe, | ||
| 215 | }; | ||
| 216 | |||
| 217 | static struct drm_fb_helper * get_fb(struct fb_info *fbi) | ||
| 218 | { | ||
| 219 | if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { | ||
| 220 | /* these are not the fb's you're looking for */ | ||
| 221 | return NULL; | ||
| 222 | } | ||
| 223 | return fbi->par; | ||
| 224 | } | ||
| 225 | |||
| 226 | /* flush an area of the framebuffer (in case of manual update display that | ||
| 227 | * is not automatically flushed) | ||
| 228 | */ | ||
| 229 | static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h) | ||
| 230 | { | ||
| 231 | struct drm_fb_helper *helper = get_fb(fbi); | ||
| 232 | |||
| 233 | if (!helper) | ||
| 234 | return; | ||
| 235 | |||
| 236 | VERB("flush fbdev: %d,%d %dx%d, fbi=%p", x, y, w, h, fbi); | ||
| 237 | |||
| 238 | omap_framebuffer_flush(helper->fb, x, y, w, h); | ||
| 239 | } | ||
| 240 | EXPORT_SYMBOL(omap_fbdev_flush); | ||
| 241 | |||
| 242 | /* initialize fbdev helper */ | ||
| 243 | struct drm_fb_helper * omap_fbdev_init(struct drm_device *dev) | ||
| 244 | { | ||
| 245 | struct omap_drm_private *priv = dev->dev_private; | ||
| 246 | struct omap_fbdev *fbdev = NULL; | ||
| 247 | struct drm_fb_helper *helper; | ||
| 248 | int ret = 0; | ||
| 249 | |||
| 250 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | ||
| 251 | if (!fbdev) { | ||
| 252 | dev_err(dev->dev, "could not allocate fbdev\n"); | ||
| 253 | goto fail; | ||
| 254 | } | ||
| 255 | |||
| 256 | helper = &fbdev->base; | ||
| 257 | |||
| 258 | helper->funcs = &omap_fb_helper_funcs; | ||
| 259 | |||
| 260 | ret = drm_fb_helper_init(dev, helper, | ||
| 261 | priv->num_crtcs, priv->num_connectors); | ||
| 262 | if (ret) { | ||
| 263 | dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); | ||
| 264 | goto fail; | ||
| 265 | } | ||
| 266 | |||
| 267 | drm_fb_helper_single_add_all_connectors(helper); | ||
| 268 | drm_fb_helper_initial_config(helper, 32); | ||
| 269 | |||
| 270 | priv->fbdev = helper; | ||
| 271 | |||
| 272 | return helper; | ||
| 273 | |||
| 274 | fail: | ||
| 275 | if (fbdev) { | ||
| 276 | kfree(fbdev); | ||
| 277 | } | ||
| 278 | return NULL; | ||
| 279 | } | ||
