sdi.c 9.6 KB
Newer Older
T
Tomi Valkeinen 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
 * linux/drivers/video/omap2/dss/sdi.c
 *
 * Copyright (C) 2009 Nokia Corporation
 * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define DSS_SUBSYS_NAME "SDI"

#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/err.h>
25
#include <linux/regulator/consumer.h>
26
#include <linux/export.h>
27
#include <linux/platform_device.h>
28
#include <linux/string.h>
T
Tomi Valkeinen 已提交
29
#include <linux/of.h>
T
Tomi Valkeinen 已提交
30
#include <linux/component.h>
T
Tomi Valkeinen 已提交
31

32
#include <video/omapdss.h>
T
Tomi Valkeinen 已提交
33 34 35
#include "dss.h"

static struct {
36 37
	struct platform_device *pdev;

T
Tomi Valkeinen 已提交
38
	bool update_enabled;
39
	struct regulator *vdds_sdi_reg;
T
Tomi Valkeinen 已提交
40

41
	struct dss_lcd_mgr_config mgr_config;
42
	struct omap_video_timings timings;
43
	int datapairs;
44

45
	struct omap_dss_device output;
T
Tomi Valkeinen 已提交
46 47

	bool port_initialized;
48
} sdi;
49

50 51 52
struct sdi_clk_calc_ctx {
	unsigned long pck_min, pck_max;

T
Tomi Valkeinen 已提交
53
	unsigned long fck;
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
	struct dispc_clock_info dispc_cinfo;
};

static bool dpi_calc_dispc_cb(int lckd, int pckd, unsigned long lck,
		unsigned long pck, void *data)
{
	struct sdi_clk_calc_ctx *ctx = data;

	ctx->dispc_cinfo.lck_div = lckd;
	ctx->dispc_cinfo.pck_div = pckd;
	ctx->dispc_cinfo.lck = lck;
	ctx->dispc_cinfo.pck = pck;

	return true;
}

70
static bool dpi_calc_dss_cb(unsigned long fck, void *data)
71 72 73
{
	struct sdi_clk_calc_ctx *ctx = data;

74
	ctx->fck = fck;
75 76 77 78 79 80

	return dispc_div_calc(fck, ctx->pck_min, ctx->pck_max,
			dpi_calc_dispc_cb, ctx);
}

static int sdi_calc_clock_div(unsigned long pclk,
81
		unsigned long *fck,
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
		struct dispc_clock_info *dispc_cinfo)
{
	int i;
	struct sdi_clk_calc_ctx ctx;

	/*
	 * DSS fclk gives us very few possibilities, so finding a good pixel
	 * clock may not be possible. We try multiple times to find the clock,
	 * each time widening the pixel clock range we look for, up to
	 * +/- 1MHz.
	 */

	for (i = 0; i < 10; ++i) {
		bool ok;

		memset(&ctx, 0, sizeof(ctx));
		if (pclk > 1000 * i * i * i)
			ctx.pck_min = max(pclk - 1000 * i * i * i, 0lu);
		else
			ctx.pck_min = 0;
		ctx.pck_max = pclk + 1000 * i * i * i;

104
		ok = dss_div_calc(pclk, ctx.pck_min, dpi_calc_dss_cb, &ctx);
105
		if (ok) {
106
			*fck = ctx.fck;
107 108 109 110 111 112 113 114
			*dispc_cinfo = ctx.dispc_cinfo;
			return 0;
		}
	}

	return -EINVAL;
}

115
static void sdi_config_lcd_manager(struct omap_dss_device *dssdev)
T
Tomi Valkeinen 已提交
116
{
117
	struct omap_overlay_manager *mgr = sdi.output.manager;
118

119
	sdi.mgr_config.io_pad_mode = DSS_IO_PAD_MODE_BYPASS;
120

121 122 123 124 125 126
	sdi.mgr_config.stallmode = false;
	sdi.mgr_config.fifohandcheck = false;

	sdi.mgr_config.video_port_width = 24;
	sdi.mgr_config.lcden_sig_polarity = 1;

127
	dss_mgr_set_lcd_config(mgr->id, &sdi.mgr_config);
T
Tomi Valkeinen 已提交
128 129
}

130
static int sdi_display_enable(struct omap_dss_device *dssdev)
T
Tomi Valkeinen 已提交
131
{
132
	struct omap_dss_device *out = &sdi.output;
133
	struct omap_video_timings *t = &sdi.timings;
134
	unsigned long fck;
T
Tomi Valkeinen 已提交
135 136 137 138
	struct dispc_clock_info dispc_cinfo;
	unsigned long pck;
	int r;

139
	if (!out->dispc_channel_connected) {
140
		DSSERR("failed to enable display: no output/manager\n");
141 142 143
		return -ENODEV;
	}

144 145
	r = regulator_enable(sdi.vdds_sdi_reg);
	if (r)
146
		goto err_reg_enable;
147

148 149 150
	r = dispc_runtime_get();
	if (r)
		goto err_get_dispc;
T
Tomi Valkeinen 已提交
151 152

	/* 15.5.9.1.2 */
153 154
	t->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE;
	t->sync_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE;
155

156
	r = sdi_calc_clock_div(t->pixelclock, &fck, &dispc_cinfo);
T
Tomi Valkeinen 已提交
157
	if (r)
158
		goto err_calc_clock_div;
T
Tomi Valkeinen 已提交
159

160
	sdi.mgr_config.clock_info = dispc_cinfo;
T
Tomi Valkeinen 已提交
161

162
	pck = fck / dispc_cinfo.lck_div / dispc_cinfo.pck_div;
T
Tomi Valkeinen 已提交
163

164 165 166
	if (pck != t->pixelclock) {
		DSSWARN("Could not find exact pixel clock. Requested %d Hz, got %lu Hz\n",
			t->pixelclock, pck);
T
Tomi Valkeinen 已提交
167

168
		t->pixelclock = pck;
T
Tomi Valkeinen 已提交
169 170 171
	}


172
	dss_mgr_set_timings(out->manager->id, t);
T
Tomi Valkeinen 已提交
173

174
	r = dss_set_fck_rate(fck);
T
Tomi Valkeinen 已提交
175
	if (r)
176
		goto err_set_dss_clock_div;
T
Tomi Valkeinen 已提交
177

178
	sdi_config_lcd_manager(dssdev);
T
Tomi Valkeinen 已提交
179

T
Tomi Valkeinen 已提交
180 181 182 183 184 185 186 187 188 189 190
	/*
	 * LCLK and PCLK divisors are located in shadow registers, and we
	 * normally write them to DISPC registers when enabling the output.
	 * However, SDI uses pck-free as source clock for its PLL, and pck-free
	 * is affected by the divisors. And as we need the PLL before enabling
	 * the output, we need to write the divisors early.
	 *
	 * It seems just writing to the DISPC register is enough, and we don't
	 * need to care about the shadow register mechanism for pck-free. The
	 * exact reason for this is unknown.
	 */
191
	dispc_mgr_set_clock_div(out->manager->id, &sdi.mgr_config.clock_info);
192

193
	dss_sdi_init(sdi.datapairs);
194 195
	r = dss_sdi_enable();
	if (r)
196
		goto err_sdi_enable;
197
	mdelay(2);
T
Tomi Valkeinen 已提交
198

199
	r = dss_mgr_enable(out->manager->id);
200 201
	if (r)
		goto err_mgr_enable;
T
Tomi Valkeinen 已提交
202 203

	return 0;
204

205 206
err_mgr_enable:
	dss_sdi_disable();
207 208 209 210 211
err_sdi_enable:
err_set_dss_clock_div:
err_calc_clock_div:
	dispc_runtime_put();
err_get_dispc:
212
	regulator_disable(sdi.vdds_sdi_reg);
213
err_reg_enable:
T
Tomi Valkeinen 已提交
214 215 216
	return r;
}

217
static void sdi_display_disable(struct omap_dss_device *dssdev)
T
Tomi Valkeinen 已提交
218
{
219
	struct omap_overlay_manager *mgr = sdi.output.manager;
220

221
	dss_mgr_disable(mgr->id);
T
Tomi Valkeinen 已提交
222 223 224

	dss_sdi_disable();

225
	dispc_runtime_put();
T
Tomi Valkeinen 已提交
226

227
	regulator_disable(sdi.vdds_sdi_reg);
T
Tomi Valkeinen 已提交
228 229
}

230
static void sdi_set_timings(struct omap_dss_device *dssdev,
231 232
		struct omap_video_timings *timings)
{
233
	sdi.timings = *timings;
234 235
}

T
Tomi Valkeinen 已提交
236 237 238 239 240 241 242 243 244 245 246
static void sdi_get_timings(struct omap_dss_device *dssdev,
		struct omap_video_timings *timings)
{
	*timings = sdi.timings;
}

static int sdi_check_timings(struct omap_dss_device *dssdev,
			struct omap_video_timings *timings)
{
	struct omap_overlay_manager *mgr = sdi.output.manager;

247
	if (!dispc_mgr_timings_ok(mgr->id, timings))
T
Tomi Valkeinen 已提交
248 249
		return -EINVAL;

250
	if (timings->pixelclock == 0)
T
Tomi Valkeinen 已提交
251 252 253 254 255
		return -EINVAL;

	return 0;
}

256
static void sdi_set_datapairs(struct omap_dss_device *dssdev, int datapairs)
257 258 259 260
{
	sdi.datapairs = datapairs;
}

261
static int sdi_init_regulator(void)
T
Tomi Valkeinen 已提交
262
{
263
	struct regulator *vdds_sdi;
T
Tomi Valkeinen 已提交
264

265 266
	if (sdi.vdds_sdi_reg)
		return 0;
267

268
	vdds_sdi = devm_regulator_get(&sdi.pdev->dev, "vdds_sdi");
269
	if (IS_ERR(vdds_sdi)) {
270 271
		if (PTR_ERR(vdds_sdi) != -EPROBE_DEFER)
			DSSERR("can't get VDDS_SDI regulator\n");
272
		return PTR_ERR(vdds_sdi);
273 274
	}

275 276
	sdi.vdds_sdi_reg = vdds_sdi;

T
Tomi Valkeinen 已提交
277 278 279
	return 0;
}

T
Tomi Valkeinen 已提交
280 281 282 283 284 285 286 287 288 289 290 291 292 293
static int sdi_connect(struct omap_dss_device *dssdev,
		struct omap_dss_device *dst)
{
	struct omap_overlay_manager *mgr;
	int r;

	r = sdi_init_regulator();
	if (r)
		return r;

	mgr = omap_dss_get_overlay_manager(dssdev->dispc_channel);
	if (!mgr)
		return -ENODEV;

294
	r = dss_mgr_connect(mgr->id, dssdev);
T
Tomi Valkeinen 已提交
295 296 297 298 299 300 301
	if (r)
		return r;

	r = omapdss_output_set_device(dssdev, dst);
	if (r) {
		DSSERR("failed to connect output to new device: %s\n",
				dst->name);
302
		dss_mgr_disconnect(mgr->id, dssdev);
T
Tomi Valkeinen 已提交
303 304 305 306 307 308 309 310 311
		return r;
	}

	return 0;
}

static void sdi_disconnect(struct omap_dss_device *dssdev,
		struct omap_dss_device *dst)
{
312
	WARN_ON(dst != dssdev->dst);
T
Tomi Valkeinen 已提交
313

314
	if (dst != dssdev->dst)
T
Tomi Valkeinen 已提交
315 316 317 318
		return;

	omapdss_output_unset_device(dssdev);

319
	dss_mgr_disconnect(dssdev->manager->id, dssdev);
T
Tomi Valkeinen 已提交
320 321 322 323 324 325
}

static const struct omapdss_sdi_ops sdi_ops = {
	.connect = sdi_connect,
	.disconnect = sdi_disconnect,

326 327
	.enable = sdi_display_enable,
	.disable = sdi_display_disable,
T
Tomi Valkeinen 已提交
328 329

	.check_timings = sdi_check_timings,
330
	.set_timings = sdi_set_timings,
T
Tomi Valkeinen 已提交
331 332
	.get_timings = sdi_get_timings,

333
	.set_datapairs = sdi_set_datapairs,
T
Tomi Valkeinen 已提交
334 335
};

336
static void sdi_init_output(struct platform_device *pdev)
337
{
338
	struct omap_dss_device *out = &sdi.output;
339

340
	out->dev = &pdev->dev;
341
	out->id = OMAP_DSS_OUTPUT_SDI;
342
	out->output_type = OMAP_DISPLAY_TYPE_SDI;
T
Tomi Valkeinen 已提交
343
	out->name = "sdi.0";
344
	out->dispc_channel = OMAP_DSS_CHANNEL_LCD;
T
Tomi Valkeinen 已提交
345 346
	/* We have SDI only on OMAP3, where it's on port 1 */
	out->port_num = 1;
T
Tomi Valkeinen 已提交
347
	out->ops.sdi = &sdi_ops;
348
	out->owner = THIS_MODULE;
349

350
	omapdss_register_output(out);
351 352
}

353
static void sdi_uninit_output(struct platform_device *pdev)
354
{
355
	struct omap_dss_device *out = &sdi.output;
356

357
	omapdss_unregister_output(out);
358 359
}

T
Tomi Valkeinen 已提交
360
static int sdi_bind(struct device *dev, struct device *master, void *data)
361
{
T
Tomi Valkeinen 已提交
362 363
	struct platform_device *pdev = to_platform_device(dev);

364 365
	sdi.pdev = pdev;

366 367
	sdi_init_output(pdev);

T
Tomi Valkeinen 已提交
368 369 370
	return 0;
}

T
Tomi Valkeinen 已提交
371
static void sdi_unbind(struct device *dev, struct device *master, void *data)
T
Tomi Valkeinen 已提交
372
{
T
Tomi Valkeinen 已提交
373 374
	struct platform_device *pdev = to_platform_device(dev);

375
	sdi_uninit_output(pdev);
T
Tomi Valkeinen 已提交
376 377 378 379 380 381
}

static const struct component_ops sdi_component_ops = {
	.bind	= sdi_bind,
	.unbind	= sdi_unbind,
};
382

T
Tomi Valkeinen 已提交
383 384 385 386 387 388 389 390
static int sdi_probe(struct platform_device *pdev)
{
	return component_add(&pdev->dev, &sdi_component_ops);
}

static int sdi_remove(struct platform_device *pdev)
{
	component_del(&pdev->dev, &sdi_component_ops);
391 392 393 394
	return 0;
}

static struct platform_driver omap_sdi_driver = {
T
Tomi Valkeinen 已提交
395 396
	.probe		= sdi_probe,
	.remove         = sdi_remove,
397 398
	.driver         = {
		.name   = "omapdss_sdi",
T
Tomi Valkeinen 已提交
399
		.suppress_bind_attrs = true,
400 401 402
	},
};

T
Tomi Valkeinen 已提交
403
int __init sdi_init_platform_driver(void)
404
{
405
	return platform_driver_register(&omap_sdi_driver);
406 407
}

408
void sdi_uninit_platform_driver(void)
409 410
{
	platform_driver_unregister(&omap_sdi_driver);
T
Tomi Valkeinen 已提交
411
}
T
Tomi Valkeinen 已提交
412

413
int sdi_init_port(struct platform_device *pdev, struct device_node *port)
T
Tomi Valkeinen 已提交
414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
{
	struct device_node *ep;
	u32 datapairs;
	int r;

	ep = omapdss_of_get_next_endpoint(port, NULL);
	if (!ep)
		return 0;

	r = of_property_read_u32(ep, "datapairs", &datapairs);
	if (r) {
		DSSERR("failed to parse datapairs\n");
		goto err_datapairs;
	}

	sdi.datapairs = datapairs;

	of_node_put(ep);

	sdi.pdev = pdev;

	sdi_init_output(pdev);

	sdi.port_initialized = true;

	return 0;

err_datapairs:
	of_node_put(ep);

	return r;
}

447
void sdi_uninit_port(struct device_node *port)
T
Tomi Valkeinen 已提交
448 449 450 451 452 453
{
	if (!sdi.port_initialized)
		return;

	sdi_uninit_output(sdi.pdev);
}