Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1/*
  2 * DRM driver for Ilitek ILI9225 panels
  3 *
  4 * Copyright 2017 David Lechner <david@lechnology.com>
  5 *
  6 * Some code copied from mipi-dbi.c
  7 * Copyright 2016 Noralf Trønnes
  8 *
  9 * This program is free software; you can redistribute it and/or modify
 10 * it under the terms of the GNU General Public License as published by
 11 * the Free Software Foundation; either version 2 of the License, or
 12 * (at your option) any later version.
 13 */
 14
 15#include <linux/delay.h>
 16#include <linux/dma-buf.h>
 17#include <linux/gpio/consumer.h>
 18#include <linux/module.h>
 19#include <linux/property.h>
 20#include <linux/spi/spi.h>
 21#include <video/mipi_display.h>
 22
 23#include <drm/drm_fb_helper.h>
 24#include <drm/drm_gem_framebuffer_helper.h>
 25#include <drm/tinydrm/mipi-dbi.h>
 26#include <drm/tinydrm/tinydrm-helpers.h>
 27
 28#define ILI9225_DRIVER_READ_CODE	0x00
 29#define ILI9225_DRIVER_OUTPUT_CONTROL	0x01
 30#define ILI9225_LCD_AC_DRIVING_CONTROL	0x02
 31#define ILI9225_ENTRY_MODE		0x03
 32#define ILI9225_DISPLAY_CONTROL_1	0x07
 33#define ILI9225_BLANK_PERIOD_CONTROL_1	0x08
 34#define ILI9225_FRAME_CYCLE_CONTROL	0x0b
 35#define ILI9225_INTERFACE_CONTROL	0x0c
 36#define ILI9225_OSCILLATION_CONTROL	0x0f
 37#define ILI9225_POWER_CONTROL_1		0x10
 38#define ILI9225_POWER_CONTROL_2		0x11
 39#define ILI9225_POWER_CONTROL_3		0x12
 40#define ILI9225_POWER_CONTROL_4		0x13
 41#define ILI9225_POWER_CONTROL_5		0x14
 42#define ILI9225_VCI_RECYCLING		0x15
 43#define ILI9225_RAM_ADDRESS_SET_1	0x20
 44#define ILI9225_RAM_ADDRESS_SET_2	0x21
 45#define ILI9225_WRITE_DATA_TO_GRAM	0x22
 46#define ILI9225_SOFTWARE_RESET		0x28
 47#define ILI9225_GATE_SCAN_CONTROL	0x30
 48#define ILI9225_VERTICAL_SCROLL_1	0x31
 49#define ILI9225_VERTICAL_SCROLL_2	0x32
 50#define ILI9225_VERTICAL_SCROLL_3	0x33
 51#define ILI9225_PARTIAL_DRIVING_POS_1	0x34
 52#define ILI9225_PARTIAL_DRIVING_POS_2	0x35
 53#define ILI9225_HORIZ_WINDOW_ADDR_1	0x36
 54#define ILI9225_HORIZ_WINDOW_ADDR_2	0x37
 55#define ILI9225_VERT_WINDOW_ADDR_1	0x38
 56#define ILI9225_VERT_WINDOW_ADDR_2	0x39
 57#define ILI9225_GAMMA_CONTROL_1		0x50
 58#define ILI9225_GAMMA_CONTROL_2		0x51
 59#define ILI9225_GAMMA_CONTROL_3		0x52
 60#define ILI9225_GAMMA_CONTROL_4		0x53
 61#define ILI9225_GAMMA_CONTROL_5		0x54
 62#define ILI9225_GAMMA_CONTROL_6		0x55
 63#define ILI9225_GAMMA_CONTROL_7		0x56
 64#define ILI9225_GAMMA_CONTROL_8		0x57
 65#define ILI9225_GAMMA_CONTROL_9		0x58
 66#define ILI9225_GAMMA_CONTROL_10	0x59
 67
 68static inline int ili9225_command(struct mipi_dbi *mipi, u8 cmd, u16 data)
 69{
 70	u8 par[2] = { data >> 8, data & 0xff };
 71
 72	return mipi_dbi_command_buf(mipi, cmd, par, 2);
 73}
 74
 75static int ili9225_fb_dirty(struct drm_framebuffer *fb,
 76			    struct drm_file *file_priv, unsigned int flags,
 77			    unsigned int color, struct drm_clip_rect *clips,
 78			    unsigned int num_clips)
 79{
 80	struct drm_gem_cma_object *cma_obj = drm_fb_cma_get_gem_obj(fb, 0);
 81	struct tinydrm_device *tdev = fb->dev->dev_private;
 82	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
 83	bool swap = mipi->swap_bytes;
 84	struct drm_clip_rect clip;
 85	u16 x_start, y_start;
 86	u16 x1, x2, y1, y2;
 87	int ret = 0;
 88	bool full;
 89	void *tr;
 90
 91	mutex_lock(&tdev->dirty_lock);
 92
 93	if (!mipi->enabled)
 94		goto out_unlock;
 95
 96	/* fbdev can flush even when we're not interested */
 97	if (tdev->pipe.plane.fb != fb)
 98		goto out_unlock;
 99
100	full = tinydrm_merge_clips(&clip, clips, num_clips, flags,
101				   fb->width, fb->height);
102
103	DRM_DEBUG("Flushing [FB:%d] x1=%u, x2=%u, y1=%u, y2=%u\n", fb->base.id,
104		  clip.x1, clip.x2, clip.y1, clip.y2);
105
106	if (!mipi->dc || !full || swap ||
107	    fb->format->format == DRM_FORMAT_XRGB8888) {
108		tr = mipi->tx_buf;
109		ret = mipi_dbi_buf_copy(mipi->tx_buf, fb, &clip, swap);
110		if (ret)
111			goto out_unlock;
112	} else {
113		tr = cma_obj->vaddr;
114	}
115
116	switch (mipi->rotation) {
117	default:
118		x1 = clip.x1;
119		x2 = clip.x2 - 1;
120		y1 = clip.y1;
121		y2 = clip.y2 - 1;
122		x_start = x1;
123		y_start = y1;
124		break;
125	case 90:
126		x1 = clip.y1;
127		x2 = clip.y2 - 1;
128		y1 = fb->width - clip.x2;
129		y2 = fb->width - clip.x1 - 1;
130		x_start = x1;
131		y_start = y2;
132		break;
133	case 180:
134		x1 = fb->width - clip.x2;
135		x2 = fb->width - clip.x1 - 1;
136		y1 = fb->height - clip.y2;
137		y2 = fb->height - clip.y1 - 1;
138		x_start = x2;
139		y_start = y2;
140		break;
141	case 270:
142		x1 = fb->height - clip.y2;
143		x2 = fb->height - clip.y1 - 1;
144		y1 = clip.x1;
145		y2 = clip.x2 - 1;
146		x_start = x2;
147		y_start = y1;
148		break;
149	}
150
151	ili9225_command(mipi, ILI9225_HORIZ_WINDOW_ADDR_1, x2);
152	ili9225_command(mipi, ILI9225_HORIZ_WINDOW_ADDR_2, x1);
153	ili9225_command(mipi, ILI9225_VERT_WINDOW_ADDR_1, y2);
154	ili9225_command(mipi, ILI9225_VERT_WINDOW_ADDR_2, y1);
155
156	ili9225_command(mipi, ILI9225_RAM_ADDRESS_SET_1, x_start);
157	ili9225_command(mipi, ILI9225_RAM_ADDRESS_SET_2, y_start);
158
159	ret = mipi_dbi_command_buf(mipi, ILI9225_WRITE_DATA_TO_GRAM, tr,
160				(clip.x2 - clip.x1) * (clip.y2 - clip.y1) * 2);
161
162out_unlock:
163	mutex_unlock(&tdev->dirty_lock);
164
165	if (ret)
166		dev_err_once(fb->dev->dev, "Failed to update display %d\n",
167			     ret);
168
169	return ret;
170}
171
172static const struct drm_framebuffer_funcs ili9225_fb_funcs = {
173	.destroy	= drm_gem_fb_destroy,
174	.create_handle	= drm_gem_fb_create_handle,
175	.dirty		= ili9225_fb_dirty,
176};
177
178static void ili9225_pipe_enable(struct drm_simple_display_pipe *pipe,
179				struct drm_crtc_state *crtc_state)
180{
181	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
182	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
183	struct device *dev = tdev->drm->dev;
184	int ret;
185	u8 am_id;
186
187	DRM_DEBUG_KMS("\n");
188
189	mipi_dbi_hw_reset(mipi);
190
191	/*
192	 * There don't seem to be two example init sequences that match, so
193	 * using the one from the popular Arduino library for this display.
194	 * https://github.com/Nkawu/TFT_22_ILI9225/blob/master/src/TFT_22_ILI9225.cpp
195	 */
196
197	ret = ili9225_command(mipi, ILI9225_POWER_CONTROL_1, 0x0000);
198	if (ret) {
199		DRM_DEV_ERROR(dev, "Error sending command %d\n", ret);
200		return;
201	}
202	ili9225_command(mipi, ILI9225_POWER_CONTROL_2, 0x0000);
203	ili9225_command(mipi, ILI9225_POWER_CONTROL_3, 0x0000);
204	ili9225_command(mipi, ILI9225_POWER_CONTROL_4, 0x0000);
205	ili9225_command(mipi, ILI9225_POWER_CONTROL_5, 0x0000);
206
207	msleep(40);
208
209	ili9225_command(mipi, ILI9225_POWER_CONTROL_2, 0x0018);
210	ili9225_command(mipi, ILI9225_POWER_CONTROL_3, 0x6121);
211	ili9225_command(mipi, ILI9225_POWER_CONTROL_4, 0x006f);
212	ili9225_command(mipi, ILI9225_POWER_CONTROL_5, 0x495f);
213	ili9225_command(mipi, ILI9225_POWER_CONTROL_1, 0x0800);
214
215	msleep(10);
216
217	ili9225_command(mipi, ILI9225_POWER_CONTROL_2, 0x103b);
218
219	msleep(50);
220
221	switch (mipi->rotation) {
222	default:
223		am_id = 0x30;
224		break;
225	case 90:
226		am_id = 0x18;
227		break;
228	case 180:
229		am_id = 0x00;
230		break;
231	case 270:
232		am_id = 0x28;
233		break;
234	}
235	ili9225_command(mipi, ILI9225_DRIVER_OUTPUT_CONTROL, 0x011c);
236	ili9225_command(mipi, ILI9225_LCD_AC_DRIVING_CONTROL, 0x0100);
237	ili9225_command(mipi, ILI9225_ENTRY_MODE, 0x1000 | am_id);
238	ili9225_command(mipi, ILI9225_DISPLAY_CONTROL_1, 0x0000);
239	ili9225_command(mipi, ILI9225_BLANK_PERIOD_CONTROL_1, 0x0808);
240	ili9225_command(mipi, ILI9225_FRAME_CYCLE_CONTROL, 0x1100);
241	ili9225_command(mipi, ILI9225_INTERFACE_CONTROL, 0x0000);
242	ili9225_command(mipi, ILI9225_OSCILLATION_CONTROL, 0x0d01);
243	ili9225_command(mipi, ILI9225_VCI_RECYCLING, 0x0020);
244	ili9225_command(mipi, ILI9225_RAM_ADDRESS_SET_1, 0x0000);
245	ili9225_command(mipi, ILI9225_RAM_ADDRESS_SET_2, 0x0000);
246
247	ili9225_command(mipi, ILI9225_GATE_SCAN_CONTROL, 0x0000);
248	ili9225_command(mipi, ILI9225_VERTICAL_SCROLL_1, 0x00db);
249	ili9225_command(mipi, ILI9225_VERTICAL_SCROLL_2, 0x0000);
250	ili9225_command(mipi, ILI9225_VERTICAL_SCROLL_3, 0x0000);
251	ili9225_command(mipi, ILI9225_PARTIAL_DRIVING_POS_1, 0x00db);
252	ili9225_command(mipi, ILI9225_PARTIAL_DRIVING_POS_2, 0x0000);
253
254	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_1, 0x0000);
255	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_2, 0x0808);
256	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_3, 0x080a);
257	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_4, 0x000a);
258	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_5, 0x0a08);
259	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_6, 0x0808);
260	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_7, 0x0000);
261	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_8, 0x0a00);
262	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_9, 0x0710);
263	ili9225_command(mipi, ILI9225_GAMMA_CONTROL_10, 0x0710);
264
265	ili9225_command(mipi, ILI9225_DISPLAY_CONTROL_1, 0x0012);
266
267	msleep(50);
268
269	ili9225_command(mipi, ILI9225_DISPLAY_CONTROL_1, 0x1017);
270
271	mipi_dbi_enable_flush(mipi);
272}
273
274static void ili9225_pipe_disable(struct drm_simple_display_pipe *pipe)
275{
276	struct tinydrm_device *tdev = pipe_to_tinydrm(pipe);
277	struct mipi_dbi *mipi = mipi_dbi_from_tinydrm(tdev);
278
279	DRM_DEBUG_KMS("\n");
280
281	if (!mipi->enabled)
282		return;
283
284	ili9225_command(mipi, ILI9225_DISPLAY_CONTROL_1, 0x0000);
285	msleep(50);
286	ili9225_command(mipi, ILI9225_POWER_CONTROL_2, 0x0007);
287	msleep(50);
288	ili9225_command(mipi, ILI9225_POWER_CONTROL_1, 0x0a02);
289
290	mipi->enabled = false;
291}
292
293static int ili9225_dbi_command(struct mipi_dbi *mipi, u8 cmd, u8 *par,
294			       size_t num)
295{
296	struct spi_device *spi = mipi->spi;
297	unsigned int bpw = 8;
298	u32 speed_hz;
299	int ret;
300
301	gpiod_set_value_cansleep(mipi->dc, 0);
302	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, 1);
303	ret = tinydrm_spi_transfer(spi, speed_hz, NULL, 8, &cmd, 1);
304	if (ret || !num)
305		return ret;
306
307	if (cmd == ILI9225_WRITE_DATA_TO_GRAM && !mipi->swap_bytes)
308		bpw = 16;
309
310	gpiod_set_value_cansleep(mipi->dc, 1);
311	speed_hz = mipi_dbi_spi_cmd_max_speed(spi, num);
312
313	return tinydrm_spi_transfer(spi, speed_hz, NULL, bpw, par, num);
314}
315
316static const u32 ili9225_formats[] = {
317	DRM_FORMAT_RGB565,
318	DRM_FORMAT_XRGB8888,
319};
320
321static int ili9225_init(struct device *dev, struct mipi_dbi *mipi,
322			const struct drm_simple_display_pipe_funcs *pipe_funcs,
323			struct drm_driver *driver,
324			const struct drm_display_mode *mode,
325			unsigned int rotation)
326{
327	size_t bufsize = mode->vdisplay * mode->hdisplay * sizeof(u16);
328	struct tinydrm_device *tdev = &mipi->tinydrm;
329	int ret;
330
331	if (!mipi->command)
332		return -EINVAL;
333
334	mutex_init(&mipi->cmdlock);
335
336	mipi->tx_buf = devm_kmalloc(dev, bufsize, GFP_KERNEL);
337	if (!mipi->tx_buf)
338		return -ENOMEM;
339
340	ret = devm_tinydrm_init(dev, tdev, &ili9225_fb_funcs, driver);
341	if (ret)
342		return ret;
343
344	ret = tinydrm_display_pipe_init(tdev, pipe_funcs,
345					DRM_MODE_CONNECTOR_VIRTUAL,
346					ili9225_formats,
347					ARRAY_SIZE(ili9225_formats), mode,
348					rotation);
349	if (ret)
350		return ret;
351
352	tdev->drm->mode_config.preferred_depth = 16;
353	mipi->rotation = rotation;
354
355	drm_mode_config_reset(tdev->drm);
356
357	DRM_DEBUG_KMS("preferred_depth=%u, rotation = %u\n",
358		      tdev->drm->mode_config.preferred_depth, rotation);
359
360	return 0;
361}
362
363static const struct drm_simple_display_pipe_funcs ili9225_pipe_funcs = {
364	.enable		= ili9225_pipe_enable,
365	.disable	= ili9225_pipe_disable,
366	.update		= tinydrm_display_pipe_update,
367	.prepare_fb	= tinydrm_display_pipe_prepare_fb,
368};
369
370static const struct drm_display_mode ili9225_mode = {
371	TINYDRM_MODE(176, 220, 35, 44),
372};
373
374DEFINE_DRM_GEM_CMA_FOPS(ili9225_fops);
375
376static struct drm_driver ili9225_driver = {
377	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME |
378				  DRIVER_ATOMIC,
379	.fops			= &ili9225_fops,
380	TINYDRM_GEM_DRIVER_OPS,
381	.lastclose		= drm_fb_helper_lastclose,
382	.name			= "ili9225",
383	.desc			= "Ilitek ILI9225",
384	.date			= "20171106",
385	.major			= 1,
386	.minor			= 0,
387};
388
389static const struct of_device_id ili9225_of_match[] = {
390	{ .compatible = "vot,v220hf01a-t" },
391	{},
392};
393MODULE_DEVICE_TABLE(of, ili9225_of_match);
394
395static const struct spi_device_id ili9225_id[] = {
396	{ "v220hf01a-t", 0 },
397	{ },
398};
399MODULE_DEVICE_TABLE(spi, ili9225_id);
400
401static int ili9225_probe(struct spi_device *spi)
402{
403	struct device *dev = &spi->dev;
404	struct mipi_dbi *mipi;
405	struct gpio_desc *rs;
406	u32 rotation = 0;
407	int ret;
408
409	mipi = devm_kzalloc(dev, sizeof(*mipi), GFP_KERNEL);
410	if (!mipi)
411		return -ENOMEM;
412
413	mipi->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
414	if (IS_ERR(mipi->reset)) {
415		DRM_DEV_ERROR(dev, "Failed to get gpio 'reset'\n");
416		return PTR_ERR(mipi->reset);
417	}
418
419	rs = devm_gpiod_get(dev, "rs", GPIOD_OUT_LOW);
420	if (IS_ERR(rs)) {
421		DRM_DEV_ERROR(dev, "Failed to get gpio 'rs'\n");
422		return PTR_ERR(rs);
423	}
424
425	device_property_read_u32(dev, "rotation", &rotation);
426
427	ret = mipi_dbi_spi_init(spi, mipi, rs);
428	if (ret)
429		return ret;
430
431	/* override the command function set in  mipi_dbi_spi_init() */
432	mipi->command = ili9225_dbi_command;
433
434	ret = ili9225_init(&spi->dev, mipi, &ili9225_pipe_funcs,
435			   &ili9225_driver, &ili9225_mode, rotation);
436	if (ret)
437		return ret;
438
439	spi_set_drvdata(spi, mipi);
440
441	return devm_tinydrm_register(&mipi->tinydrm);
442}
443
444static void ili9225_shutdown(struct spi_device *spi)
445{
446	struct mipi_dbi *mipi = spi_get_drvdata(spi);
447
448	tinydrm_shutdown(&mipi->tinydrm);
449}
450
451static struct spi_driver ili9225_spi_driver = {
452	.driver = {
453		.name = "ili9225",
454		.owner = THIS_MODULE,
455		.of_match_table = ili9225_of_match,
456	},
457	.id_table = ili9225_id,
458	.probe = ili9225_probe,
459	.shutdown = ili9225_shutdown,
460};
461module_spi_driver(ili9225_spi_driver);
462
463MODULE_DESCRIPTION("Ilitek ILI9225 DRM driver");
464MODULE_AUTHOR("David Lechner <david@lechnology.com>");
465MODULE_LICENSE("GPL");