1/*
2 * Copyright (C) 2012 Russell King
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8#include <drm/drmP.h>
9#include <drm/drm_crtc_helper.h>
10#include <drm/drm_fb_helper.h>
11#include "armada_drm.h"
12#include "armada_fb.h"
13#include "armada_gem.h"
14#include "armada_hw.h"
15
16static void armada_fb_destroy(struct drm_framebuffer *fb)
17{
18	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
19
20	drm_framebuffer_cleanup(&dfb->fb);
21	drm_gem_object_unreference_unlocked(&dfb->obj->obj);
22	kfree(dfb);
23}
24
25static int armada_fb_create_handle(struct drm_framebuffer *fb,
26	struct drm_file *dfile, unsigned int *handle)
27{
28	struct armada_framebuffer *dfb = drm_fb_to_armada_fb(fb);
29	return drm_gem_handle_create(dfile, &dfb->obj->obj, handle);
30}
31
32static const struct drm_framebuffer_funcs armada_fb_funcs = {
33	.destroy	= armada_fb_destroy,
34	.create_handle	= armada_fb_create_handle,
35};
36
37struct armada_framebuffer *armada_framebuffer_create(struct drm_device *dev,
38	struct drm_mode_fb_cmd2 *mode, struct armada_gem_object *obj)
39{
40	struct armada_framebuffer *dfb;
41	uint8_t format, config;
42	int ret;
43
44	switch (mode->pixel_format) {
45#define FMT(drm, fmt, mod)		\
46	case DRM_FORMAT_##drm:		\
47		format = CFG_##fmt;	\
48		config = mod;		\
49		break
50	FMT(RGB565,	565,		CFG_SWAPRB);
51	FMT(BGR565,	565,		0);
52	FMT(ARGB1555,	1555,		CFG_SWAPRB);
53	FMT(ABGR1555,	1555,		0);
54	FMT(RGB888,	888PACK,	CFG_SWAPRB);
55	FMT(BGR888,	888PACK,	0);
56	FMT(XRGB8888,	X888,		CFG_SWAPRB);
57	FMT(XBGR8888,	X888,		0);
58	FMT(ARGB8888,	8888,		CFG_SWAPRB);
59	FMT(ABGR8888,	8888,		0);
60	FMT(YUYV,	422PACK,	CFG_YUV2RGB | CFG_SWAPYU | CFG_SWAPUV);
61	FMT(UYVY,	422PACK,	CFG_YUV2RGB);
62	FMT(VYUY,	422PACK,	CFG_YUV2RGB | CFG_SWAPUV);
63	FMT(YVYU,	422PACK,	CFG_YUV2RGB | CFG_SWAPYU);
64	FMT(YUV422,	422,		CFG_YUV2RGB);
65	FMT(YVU422,	422,		CFG_YUV2RGB | CFG_SWAPUV);
66	FMT(YUV420,	420,		CFG_YUV2RGB);
67	FMT(YVU420,	420,		CFG_YUV2RGB | CFG_SWAPUV);
68	FMT(C8,		PSEUDO8,	0);
69#undef FMT
70	default:
71		return ERR_PTR(-EINVAL);
72	}
73
74	dfb = kzalloc(sizeof(*dfb), GFP_KERNEL);
75	if (!dfb) {
76		DRM_ERROR("failed to allocate Armada fb object\n");
77		return ERR_PTR(-ENOMEM);
78	}
79
80	dfb->fmt = format;
81	dfb->mod = config;
82	dfb->obj = obj;
83
84	drm_helper_mode_fill_fb_struct(&dfb->fb, mode);
85
86	ret = drm_framebuffer_init(dev, &dfb->fb, &armada_fb_funcs);
87	if (ret) {
88		kfree(dfb);
89		return ERR_PTR(ret);
90	}
91
92	/*
93	 * Take a reference on our object as we're successful - the
94	 * caller already holds a reference, which keeps us safe for
95	 * the above call, but the caller will drop their reference
96	 * to it.  Hence we need to take our own reference.
97	 */
98	drm_gem_object_reference(&obj->obj);
99
100	return dfb;
101}
102
103static struct drm_framebuffer *armada_fb_create(struct drm_device *dev,
104	struct drm_file *dfile, struct drm_mode_fb_cmd2 *mode)
105{
106	struct armada_gem_object *obj;
107	struct armada_framebuffer *dfb;
108	int ret;
109
110	DRM_DEBUG_DRIVER("w%u h%u pf%08x f%u p%u,%u,%u\n",
111		mode->width, mode->height, mode->pixel_format,
112		mode->flags, mode->pitches[0], mode->pitches[1],
113		mode->pitches[2]);
114
115	/* We can only handle a single plane at the moment */
116	if (drm_format_num_planes(mode->pixel_format) > 1 &&
117	    (mode->handles[0] != mode->handles[1] ||
118	     mode->handles[0] != mode->handles[2])) {
119		ret = -EINVAL;
120		goto err;
121	}
122
123	obj = armada_gem_object_lookup(dev, dfile, mode->handles[0]);
124	if (!obj) {
125		ret = -ENOENT;
126		goto err;
127	}
128
129	if (obj->obj.import_attach && !obj->sgt) {
130		ret = armada_gem_map_import(obj);
131		if (ret)
132			goto err_unref;
133	}
134
135	/* Framebuffer objects must have a valid device address for scanout */
136	if (obj->dev_addr == DMA_ERROR_CODE) {
137		ret = -EINVAL;
138		goto err_unref;
139	}
140
141	dfb = armada_framebuffer_create(dev, mode, obj);
142	if (IS_ERR(dfb)) {
143		ret = PTR_ERR(dfb);
144		goto err;
145	}
146
147	drm_gem_object_unreference_unlocked(&obj->obj);
148
149	return &dfb->fb;
150
151 err_unref:
152	drm_gem_object_unreference_unlocked(&obj->obj);
153 err:
154	DRM_ERROR("failed to initialize framebuffer: %d\n", ret);
155	return ERR_PTR(ret);
156}
157
158static void armada_output_poll_changed(struct drm_device *dev)
159{
160	struct armada_private *priv = dev->dev_private;
161	struct drm_fb_helper *fbh = priv->fbdev;
162
163	if (fbh)
164		drm_fb_helper_hotplug_event(fbh);
165}
166
167const struct drm_mode_config_funcs armada_drm_mode_config_funcs = {
168	.fb_create		= armada_fb_create,
169	.output_poll_changed	= armada_output_poll_changed,
170};
171