1/*
2 * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd
3 * Author:Mark Yao <mark.yao@rock-chips.com>
4 *
5 * This software is licensed under the terms of the GNU General Public
6 * License version 2, as published by the Free Software Foundation, and
7 * may be copied, distributed, and modified under those terms.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 */
14
15#include <linux/kernel.h>
16#include <drm/drm.h>
17#include <drm/drmP.h>
18#include <drm/drm_fb_helper.h>
19#include <drm/drm_crtc_helper.h>
20
21#include "rockchip_drm_drv.h"
22#include "rockchip_drm_gem.h"
23
24#define to_rockchip_fb(x) container_of(x, struct rockchip_drm_fb, fb)
25
26struct rockchip_drm_fb {
27	struct drm_framebuffer fb;
28	struct drm_gem_object *obj[ROCKCHIP_MAX_FB_BUFFER];
29};
30
31struct drm_gem_object *rockchip_fb_get_gem_obj(struct drm_framebuffer *fb,
32					       unsigned int plane)
33{
34	struct rockchip_drm_fb *rk_fb = to_rockchip_fb(fb);
35
36	if (plane >= ROCKCHIP_MAX_FB_BUFFER)
37		return NULL;
38
39	return rk_fb->obj[plane];
40}
41EXPORT_SYMBOL_GPL(rockchip_fb_get_gem_obj);
42
43static void rockchip_drm_fb_destroy(struct drm_framebuffer *fb)
44{
45	struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
46	struct drm_gem_object *obj;
47	int i;
48
49	for (i = 0; i < ROCKCHIP_MAX_FB_BUFFER; i++) {
50		obj = rockchip_fb->obj[i];
51		if (obj)
52			drm_gem_object_unreference_unlocked(obj);
53	}
54
55	drm_framebuffer_cleanup(fb);
56	kfree(rockchip_fb);
57}
58
59static int rockchip_drm_fb_create_handle(struct drm_framebuffer *fb,
60					 struct drm_file *file_priv,
61					 unsigned int *handle)
62{
63	struct rockchip_drm_fb *rockchip_fb = to_rockchip_fb(fb);
64
65	return drm_gem_handle_create(file_priv,
66				     rockchip_fb->obj[0], handle);
67}
68
69static struct drm_framebuffer_funcs rockchip_drm_fb_funcs = {
70	.destroy	= rockchip_drm_fb_destroy,
71	.create_handle	= rockchip_drm_fb_create_handle,
72};
73
74static struct rockchip_drm_fb *
75rockchip_fb_alloc(struct drm_device *dev, struct drm_mode_fb_cmd2 *mode_cmd,
76		  struct drm_gem_object **obj, unsigned int num_planes)
77{
78	struct rockchip_drm_fb *rockchip_fb;
79	int ret;
80	int i;
81
82	rockchip_fb = kzalloc(sizeof(*rockchip_fb), GFP_KERNEL);
83	if (!rockchip_fb)
84		return ERR_PTR(-ENOMEM);
85
86	drm_helper_mode_fill_fb_struct(&rockchip_fb->fb, mode_cmd);
87
88	for (i = 0; i < num_planes; i++)
89		rockchip_fb->obj[i] = obj[i];
90
91	ret = drm_framebuffer_init(dev, &rockchip_fb->fb,
92				   &rockchip_drm_fb_funcs);
93	if (ret) {
94		dev_err(dev->dev, "Failed to initialize framebuffer: %d\n",
95			ret);
96		kfree(rockchip_fb);
97		return ERR_PTR(ret);
98	}
99
100	return rockchip_fb;
101}
102
103static struct drm_framebuffer *
104rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv,
105			struct drm_mode_fb_cmd2 *mode_cmd)
106{
107	struct rockchip_drm_fb *rockchip_fb;
108	struct drm_gem_object *objs[ROCKCHIP_MAX_FB_BUFFER];
109	struct drm_gem_object *obj;
110	unsigned int hsub;
111	unsigned int vsub;
112	int num_planes;
113	int ret;
114	int i;
115
116	hsub = drm_format_horz_chroma_subsampling(mode_cmd->pixel_format);
117	vsub = drm_format_vert_chroma_subsampling(mode_cmd->pixel_format);
118	num_planes = min(drm_format_num_planes(mode_cmd->pixel_format),
119			 ROCKCHIP_MAX_FB_BUFFER);
120
121	for (i = 0; i < num_planes; i++) {
122		unsigned int width = mode_cmd->width / (i ? hsub : 1);
123		unsigned int height = mode_cmd->height / (i ? vsub : 1);
124		unsigned int min_size;
125
126		obj = drm_gem_object_lookup(dev, file_priv,
127					    mode_cmd->handles[i]);
128		if (!obj) {
129			dev_err(dev->dev, "Failed to lookup GEM object\n");
130			ret = -ENXIO;
131			goto err_gem_object_unreference;
132		}
133
134		min_size = (height - 1) * mode_cmd->pitches[i] +
135			mode_cmd->offsets[i] +
136			width * drm_format_plane_cpp(mode_cmd->pixel_format, i);
137
138		if (obj->size < min_size) {
139			drm_gem_object_unreference_unlocked(obj);
140			ret = -EINVAL;
141			goto err_gem_object_unreference;
142		}
143		objs[i] = obj;
144	}
145
146	rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, objs, i);
147	if (IS_ERR(rockchip_fb)) {
148		ret = PTR_ERR(rockchip_fb);
149		goto err_gem_object_unreference;
150	}
151
152	return &rockchip_fb->fb;
153
154err_gem_object_unreference:
155	for (i--; i >= 0; i--)
156		drm_gem_object_unreference_unlocked(objs[i]);
157	return ERR_PTR(ret);
158}
159
160static void rockchip_drm_output_poll_changed(struct drm_device *dev)
161{
162	struct rockchip_drm_private *private = dev->dev_private;
163	struct drm_fb_helper *fb_helper = &private->fbdev_helper;
164
165	if (fb_helper)
166		drm_fb_helper_hotplug_event(fb_helper);
167}
168
169static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = {
170	.fb_create = rockchip_user_fb_create,
171	.output_poll_changed = rockchip_drm_output_poll_changed,
172};
173
174struct drm_framebuffer *
175rockchip_drm_framebuffer_init(struct drm_device *dev,
176			      struct drm_mode_fb_cmd2 *mode_cmd,
177			      struct drm_gem_object *obj)
178{
179	struct rockchip_drm_fb *rockchip_fb;
180
181	rockchip_fb = rockchip_fb_alloc(dev, mode_cmd, &obj, 1);
182	if (IS_ERR(rockchip_fb))
183		return NULL;
184
185	return &rockchip_fb->fb;
186}
187
188void rockchip_drm_mode_config_init(struct drm_device *dev)
189{
190	dev->mode_config.min_width = 0;
191	dev->mode_config.min_height = 0;
192
193	/*
194	 * set max width and height as default value(4096x4096).
195	 * this value would be used to check framebuffer size limitation
196	 * at drm_mode_addfb().
197	 */
198	dev->mode_config.max_width = 4096;
199	dev->mode_config.max_height = 4096;
200
201	dev->mode_config.funcs = &rockchip_drm_mode_config_funcs;
202}
203