1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright (C) 2009 Nokia Corporation
4 * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
5 *
6 * Some code and ideas taken from drivers/video/omap/ driver
7 * by Imre Deak.
8 */
9
10#define DSS_SUBSYS_NAME "DISPLAY"
11
12#include <linux/kernel.h>
13#include <linux/kstrtox.h>
14#include <linux/module.h>
15#include <linux/platform_device.h>
16#include <linux/sysfs.h>
17
18#include <video/omapfb_dss.h>
19#include "dss.h"
20
21static ssize_t display_name_show(struct omap_dss_device *dssdev, char *buf)
22{
23	return sysfs_emit(buf, "%s\n",
24			dssdev->name ?
25			dssdev->name : "");
26}
27
28static ssize_t display_enabled_show(struct omap_dss_device *dssdev, char *buf)
29{
30	return sysfs_emit(buf, "%d\n",
31			omapdss_device_is_enabled(dssdev));
32}
33
34static ssize_t display_enabled_store(struct omap_dss_device *dssdev,
35		const char *buf, size_t size)
36{
37	int r;
38	bool enable;
39
40	r = kstrtobool(buf, &enable);
41	if (r)
42		return r;
43
44	if (enable == omapdss_device_is_enabled(dssdev))
45		return size;
46
47	if (omapdss_device_is_connected(dssdev) == false)
48		return -ENODEV;
49
50	if (enable) {
51		r = dssdev->driver->enable(dssdev);
52		if (r)
53			return r;
54	} else {
55		dssdev->driver->disable(dssdev);
56	}
57
58	return size;
59}
60
61static ssize_t display_tear_show(struct omap_dss_device *dssdev, char *buf)
62{
63	return sysfs_emit(buf, "%d\n",
64			dssdev->driver->get_te ?
65			dssdev->driver->get_te(dssdev) : 0);
66}
67
68static ssize_t display_tear_store(struct omap_dss_device *dssdev,
69	const char *buf, size_t size)
70{
71	int r;
72	bool te;
73
74	if (!dssdev->driver->enable_te || !dssdev->driver->get_te)
75		return -ENOENT;
76
77	r = kstrtobool(buf, &te);
78	if (r)
79		return r;
80
81	r = dssdev->driver->enable_te(dssdev, te);
82	if (r)
83		return r;
84
85	return size;
86}
87
88static ssize_t display_timings_show(struct omap_dss_device *dssdev, char *buf)
89{
90	struct omap_video_timings t;
91
92	if (!dssdev->driver->get_timings)
93		return -ENOENT;
94
95	dssdev->driver->get_timings(dssdev, &t);
96
97	return sysfs_emit(buf, "%u,%u/%u/%u/%u,%u/%u/%u/%u\n",
98			t.pixelclock,
99			t.x_res, t.hfp, t.hbp, t.hsw,
100			t.y_res, t.vfp, t.vbp, t.vsw);
101}
102
103static ssize_t display_timings_store(struct omap_dss_device *dssdev,
104	const char *buf, size_t size)
105{
106	struct omap_video_timings t = dssdev->panel.timings;
107	int r, found;
108
109	if (!dssdev->driver->set_timings || !dssdev->driver->check_timings)
110		return -ENOENT;
111
112	found = 0;
113#ifdef CONFIG_FB_OMAP2_DSS_VENC
114	if (strncmp("pal", buf, 3) == 0) {
115		t = omap_dss_pal_timings;
116		found = 1;
117	} else if (strncmp("ntsc", buf, 4) == 0) {
118		t = omap_dss_ntsc_timings;
119		found = 1;
120	}
121#endif
122	if (!found && sscanf(buf, "%u,%hu/%hu/%hu/%hu,%hu/%hu/%hu/%hu",
123				&t.pixelclock,
124				&t.x_res, &t.hfp, &t.hbp, &t.hsw,
125				&t.y_res, &t.vfp, &t.vbp, &t.vsw) != 9)
126		return -EINVAL;
127
128	r = dssdev->driver->check_timings(dssdev, &t);
129	if (r)
130		return r;
131
132	dssdev->driver->disable(dssdev);
133	dssdev->driver->set_timings(dssdev, &t);
134	r = dssdev->driver->enable(dssdev);
135	if (r)
136		return r;
137
138	return size;
139}
140
141static ssize_t display_rotate_show(struct omap_dss_device *dssdev, char *buf)
142{
143	int rotate;
144	if (!dssdev->driver->get_rotate)
145		return -ENOENT;
146	rotate = dssdev->driver->get_rotate(dssdev);
147	return sysfs_emit(buf, "%u\n", rotate);
148}
149
150static ssize_t display_rotate_store(struct omap_dss_device *dssdev,
151	const char *buf, size_t size)
152{
153	int rot, r;
154
155	if (!dssdev->driver->set_rotate || !dssdev->driver->get_rotate)
156		return -ENOENT;
157
158	r = kstrtoint(buf, 0, &rot);
159	if (r)
160		return r;
161
162	r = dssdev->driver->set_rotate(dssdev, rot);
163	if (r)
164		return r;
165
166	return size;
167}
168
169static ssize_t display_mirror_show(struct omap_dss_device *dssdev, char *buf)
170{
171	int mirror;
172	if (!dssdev->driver->get_mirror)
173		return -ENOENT;
174	mirror = dssdev->driver->get_mirror(dssdev);
175	return sysfs_emit(buf, "%u\n", mirror);
176}
177
178static ssize_t display_mirror_store(struct omap_dss_device *dssdev,
179	const char *buf, size_t size)
180{
181	int r;
182	bool mirror;
183
184	if (!dssdev->driver->set_mirror || !dssdev->driver->get_mirror)
185		return -ENOENT;
186
187	r = kstrtobool(buf, &mirror);
188	if (r)
189		return r;
190
191	r = dssdev->driver->set_mirror(dssdev, mirror);
192	if (r)
193		return r;
194
195	return size;
196}
197
198static ssize_t display_wss_show(struct omap_dss_device *dssdev, char *buf)
199{
200	unsigned int wss;
201
202	if (!dssdev->driver->get_wss)
203		return -ENOENT;
204
205	wss = dssdev->driver->get_wss(dssdev);
206
207	return sysfs_emit(buf, "0x%05x\n", wss);
208}
209
210static ssize_t display_wss_store(struct omap_dss_device *dssdev,
211	const char *buf, size_t size)
212{
213	u32 wss;
214	int r;
215
216	if (!dssdev->driver->get_wss || !dssdev->driver->set_wss)
217		return -ENOENT;
218
219	r = kstrtou32(buf, 0, &wss);
220	if (r)
221		return r;
222
223	if (wss > 0xfffff)
224		return -EINVAL;
225
226	r = dssdev->driver->set_wss(dssdev, wss);
227	if (r)
228		return r;
229
230	return size;
231}
232
233struct display_attribute {
234	struct attribute attr;
235	ssize_t (*show)(struct omap_dss_device *, char *);
236	ssize_t	(*store)(struct omap_dss_device *, const char *, size_t);
237};
238
239#define DISPLAY_ATTR(_name, _mode, _show, _store) \
240	struct display_attribute display_attr_##_name = \
241	__ATTR(_name, _mode, _show, _store)
242
243static DISPLAY_ATTR(name, S_IRUGO, display_name_show, NULL);
244static DISPLAY_ATTR(display_name, S_IRUGO, display_name_show, NULL);
245static DISPLAY_ATTR(enabled, S_IRUGO|S_IWUSR,
246		display_enabled_show, display_enabled_store);
247static DISPLAY_ATTR(tear_elim, S_IRUGO|S_IWUSR,
248		display_tear_show, display_tear_store);
249static DISPLAY_ATTR(timings, S_IRUGO|S_IWUSR,
250		display_timings_show, display_timings_store);
251static DISPLAY_ATTR(rotate, S_IRUGO|S_IWUSR,
252		display_rotate_show, display_rotate_store);
253static DISPLAY_ATTR(mirror, S_IRUGO|S_IWUSR,
254		display_mirror_show, display_mirror_store);
255static DISPLAY_ATTR(wss, S_IRUGO|S_IWUSR,
256		display_wss_show, display_wss_store);
257
258static struct attribute *display_sysfs_attrs[] = {
259	&display_attr_name.attr,
260	&display_attr_display_name.attr,
261	&display_attr_enabled.attr,
262	&display_attr_tear_elim.attr,
263	&display_attr_timings.attr,
264	&display_attr_rotate.attr,
265	&display_attr_mirror.attr,
266	&display_attr_wss.attr,
267	NULL
268};
269ATTRIBUTE_GROUPS(display_sysfs);
270
271static ssize_t display_attr_show(struct kobject *kobj, struct attribute *attr,
272		char *buf)
273{
274	struct omap_dss_device *dssdev;
275	struct display_attribute *display_attr;
276
277	dssdev = container_of(kobj, struct omap_dss_device, kobj);
278	display_attr = container_of(attr, struct display_attribute, attr);
279
280	if (!display_attr->show)
281		return -ENOENT;
282
283	return display_attr->show(dssdev, buf);
284}
285
286static ssize_t display_attr_store(struct kobject *kobj, struct attribute *attr,
287		const char *buf, size_t size)
288{
289	struct omap_dss_device *dssdev;
290	struct display_attribute *display_attr;
291
292	dssdev = container_of(kobj, struct omap_dss_device, kobj);
293	display_attr = container_of(attr, struct display_attribute, attr);
294
295	if (!display_attr->store)
296		return -ENOENT;
297
298	return display_attr->store(dssdev, buf, size);
299}
300
301static const struct sysfs_ops display_sysfs_ops = {
302	.show = display_attr_show,
303	.store = display_attr_store,
304};
305
306static struct kobj_type display_ktype = {
307	.sysfs_ops = &display_sysfs_ops,
308	.default_groups = display_sysfs_groups,
309};
310
311int display_init_sysfs(struct platform_device *pdev)
312{
313	struct omap_dss_device *dssdev = NULL;
314	int r;
315
316	for_each_dss_dev(dssdev) {
317		r = kobject_init_and_add(&dssdev->kobj, &display_ktype,
318			&pdev->dev.kobj, "%s", dssdev->alias);
319		if (r) {
320			DSSERR("failed to create sysfs files\n");
321			omap_dss_put_device(dssdev);
322			goto err;
323		}
324	}
325
326	return 0;
327
328err:
329	display_uninit_sysfs(pdev);
330
331	return r;
332}
333
334void display_uninit_sysfs(struct platform_device *pdev)
335{
336	struct omap_dss_device *dssdev = NULL;
337
338	for_each_dss_dev(dssdev) {
339		if (kobject_name(&dssdev->kobj) == NULL)
340			continue;
341
342		kobject_del(&dssdev->kobj);
343		kobject_put(&dssdev->kobj);
344
345		memset(&dssdev->kobj, 0, sizeof(dssdev->kobj));
346	}
347}
348