File Index
Files and directories
Sign in
FILE

ams369fg06.c

File preview with syntax highlighting.

linux-6.19.14/drivers/video/backlight/ams369fg06.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * ams369fg06 AMOLED LCD panel driver.
  4 *
  5 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
  6 * Author: Jingoo Han  <jg1.han@samsung.com>
  7 *
  8 * Derived from drivers/video/s6e63m0.c
  9 */
 10
 11#include <linux/backlight.h>
 12#include <linux/delay.h>
 13#include <linux/lcd.h>
 14#include <linux/module.h>
 15#include <linux/spi/spi.h>
 16#include <linux/wait.h>
 17
 18#define SLEEPMSEC		0x1000
 19#define ENDDEF			0x2000
 20#define	DEFMASK			0xFF00
 21#define COMMAND_ONLY		0xFE
 22#define DATA_ONLY		0xFF
 23
 24#define MAX_GAMMA_LEVEL		5
 25#define GAMMA_TABLE_COUNT	21
 26
 27#define MIN_BRIGHTNESS		0
 28#define MAX_BRIGHTNESS		255
 29#define DEFAULT_BRIGHTNESS	150
 30
 31struct ams369fg06 {
 32	struct device			*dev;
 33	struct spi_device		*spi;
 34	unsigned int			power;
 35	struct lcd_device		*ld;
 36	struct backlight_device		*bd;
 37	struct lcd_platform_data	*lcd_pd;
 38};
 39
 40static const unsigned short seq_display_on[] = {
 41	0x14, 0x03,
 42	ENDDEF, 0x0000
 43};
 44
 45static const unsigned short seq_display_off[] = {
 46	0x14, 0x00,
 47	ENDDEF, 0x0000
 48};
 49
 50static const unsigned short seq_stand_by_on[] = {
 51	0x1D, 0xA1,
 52	SLEEPMSEC, 200,
 53	ENDDEF, 0x0000
 54};
 55
 56static const unsigned short seq_stand_by_off[] = {
 57	0x1D, 0xA0,
 58	SLEEPMSEC, 250,
 59	ENDDEF, 0x0000
 60};
 61
 62static const unsigned short seq_setting[] = {
 63	0x31, 0x08,
 64	0x32, 0x14,
 65	0x30, 0x02,
 66	0x27, 0x01,
 67	0x12, 0x08,
 68	0x13, 0x08,
 69	0x15, 0x00,
 70	0x16, 0x00,
 71
 72	0xef, 0xd0,
 73	DATA_ONLY, 0xe8,
 74
 75	0x39, 0x44,
 76	0x40, 0x00,
 77	0x41, 0x3f,
 78	0x42, 0x2a,
 79	0x43, 0x27,
 80	0x44, 0x27,
 81	0x45, 0x1f,
 82	0x46, 0x44,
 83	0x50, 0x00,
 84	0x51, 0x00,
 85	0x52, 0x17,
 86	0x53, 0x24,
 87	0x54, 0x26,
 88	0x55, 0x1f,
 89	0x56, 0x43,
 90	0x60, 0x00,
 91	0x61, 0x3f,
 92	0x62, 0x2a,
 93	0x63, 0x25,
 94	0x64, 0x24,
 95	0x65, 0x1b,
 96	0x66, 0x5c,
 97
 98	0x17, 0x22,
 99	0x18, 0x33,
100	0x19, 0x03,
101	0x1a, 0x01,
102	0x22, 0xa4,
103	0x23, 0x00,
104	0x26, 0xa0,
105
106	0x1d, 0xa0,
107	SLEEPMSEC, 300,
108
109	0x14, 0x03,
110
111	ENDDEF, 0x0000
112};
113
114/* gamma value: 2.2 */
115static const unsigned int ams369fg06_22_250[] = {
116	0x00, 0x3f, 0x2a, 0x27, 0x27, 0x1f, 0x44,
117	0x00, 0x00, 0x17, 0x24, 0x26, 0x1f, 0x43,
118	0x00, 0x3f, 0x2a, 0x25, 0x24, 0x1b, 0x5c,
119};
120
121static const unsigned int ams369fg06_22_200[] = {
122	0x00, 0x3f, 0x28, 0x29, 0x27, 0x21, 0x3e,
123	0x00, 0x00, 0x10, 0x25, 0x27, 0x20, 0x3d,
124	0x00, 0x3f, 0x28, 0x27, 0x25, 0x1d, 0x53,
125};
126
127static const unsigned int ams369fg06_22_150[] = {
128	0x00, 0x3f, 0x2d, 0x29, 0x28, 0x23, 0x37,
129	0x00, 0x00, 0x0b, 0x25, 0x28, 0x22, 0x36,
130	0x00, 0x3f, 0x2b, 0x28, 0x26, 0x1f, 0x4a,
131};
132
133static const unsigned int ams369fg06_22_100[] = {
134	0x00, 0x3f, 0x30, 0x2a, 0x2b, 0x24, 0x2f,
135	0x00, 0x00, 0x00, 0x25, 0x29, 0x24, 0x2e,
136	0x00, 0x3f, 0x2f, 0x29, 0x29, 0x21, 0x3f,
137};
138
139static const unsigned int ams369fg06_22_50[] = {
140	0x00, 0x3f, 0x3c, 0x2c, 0x2d, 0x27, 0x24,
141	0x00, 0x00, 0x00, 0x22, 0x2a, 0x27, 0x23,
142	0x00, 0x3f, 0x3b, 0x2c, 0x2b, 0x24, 0x31,
143};
144
145struct ams369fg06_gamma {
146	unsigned int *gamma_22_table[MAX_GAMMA_LEVEL];
147};
148
149static struct ams369fg06_gamma gamma_table = {
150	.gamma_22_table[0] = (unsigned int *)&ams369fg06_22_50,
151	.gamma_22_table[1] = (unsigned int *)&ams369fg06_22_100,
152	.gamma_22_table[2] = (unsigned int *)&ams369fg06_22_150,
153	.gamma_22_table[3] = (unsigned int *)&ams369fg06_22_200,
154	.gamma_22_table[4] = (unsigned int *)&ams369fg06_22_250,
155};
156
157static int ams369fg06_spi_write_byte(struct ams369fg06 *lcd, int addr, int data)
158{
159	u16 buf[1];
160	struct spi_message msg;
161
162	struct spi_transfer xfer = {
163		.len		= 2,
164		.tx_buf		= buf,
165	};
166
167	buf[0] = (addr << 8) | data;
168
169	spi_message_init(&msg);
170	spi_message_add_tail(&xfer, &msg);
171
172	return spi_sync(lcd->spi, &msg);
173}
174
175static int ams369fg06_spi_write(struct ams369fg06 *lcd, unsigned char address,
176	unsigned char command)
177{
178	int ret = 0;
179
180	if (address != DATA_ONLY)
181		ret = ams369fg06_spi_write_byte(lcd, 0x70, address);
182	if (command != COMMAND_ONLY)
183		ret = ams369fg06_spi_write_byte(lcd, 0x72, command);
184
185	return ret;
186}
187
188static int ams369fg06_panel_send_sequence(struct ams369fg06 *lcd,
189	const unsigned short *wbuf)
190{
191	int ret = 0, i = 0;
192
193	while ((wbuf[i] & DEFMASK) != ENDDEF) {
194		if ((wbuf[i] & DEFMASK) != SLEEPMSEC) {
195			ret = ams369fg06_spi_write(lcd, wbuf[i], wbuf[i+1]);
196			if (ret)
197				break;
198		} else {
199			msleep(wbuf[i+1]);
200		}
201		i += 2;
202	}
203
204	return ret;
205}
206
207static int _ams369fg06_gamma_ctl(struct ams369fg06 *lcd,
208	const unsigned int *gamma)
209{
210	unsigned int i = 0;
211	int ret = 0;
212
213	for (i = 0 ; i < GAMMA_TABLE_COUNT / 3; i++) {
214		ret = ams369fg06_spi_write(lcd, 0x40 + i, gamma[i]);
215		ret = ams369fg06_spi_write(lcd, 0x50 + i, gamma[i+7*1]);
216		ret = ams369fg06_spi_write(lcd, 0x60 + i, gamma[i+7*2]);
217		if (ret) {
218			dev_err(lcd->dev, "failed to set gamma table.\n");
219			goto gamma_err;
220		}
221	}
222
223gamma_err:
224	return ret;
225}
226
227static int ams369fg06_gamma_ctl(struct ams369fg06 *lcd, int brightness)
228{
229	int ret = 0;
230	int gamma = 0;
231
232	if ((brightness >= 0) && (brightness <= 50))
233		gamma = 0;
234	else if ((brightness > 50) && (brightness <= 100))
235		gamma = 1;
236	else if ((brightness > 100) && (brightness <= 150))
237		gamma = 2;
238	else if ((brightness > 150) && (brightness <= 200))
239		gamma = 3;
240	else if ((brightness > 200) && (brightness <= 255))
241		gamma = 4;
242
243	ret = _ams369fg06_gamma_ctl(lcd, gamma_table.gamma_22_table[gamma]);
244
245	return ret;
246}
247
248static int ams369fg06_ldi_init(struct ams369fg06 *lcd)
249{
250	int ret, i;
251	static const unsigned short *init_seq[] = {
252		seq_setting,
253		seq_stand_by_off,
254	};
255
256	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
257		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
258		if (ret)
259			break;
260	}
261
262	return ret;
263}
264
265static int ams369fg06_ldi_enable(struct ams369fg06 *lcd)
266{
267	int ret, i;
268	static const unsigned short *init_seq[] = {
269		seq_stand_by_off,
270		seq_display_on,
271	};
272
273	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
274		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
275		if (ret)
276			break;
277	}
278
279	return ret;
280}
281
282static int ams369fg06_ldi_disable(struct ams369fg06 *lcd)
283{
284	int ret, i;
285
286	static const unsigned short *init_seq[] = {
287		seq_display_off,
288		seq_stand_by_on,
289	};
290
291	for (i = 0; i < ARRAY_SIZE(init_seq); i++) {
292		ret = ams369fg06_panel_send_sequence(lcd, init_seq[i]);
293		if (ret)
294			break;
295	}
296
297	return ret;
298}
299
300static int ams369fg06_power_is_on(int power)
301{
302	return power <= BACKLIGHT_POWER_REDUCED;
303}
304
305static int ams369fg06_power_on(struct ams369fg06 *lcd)
306{
307	int ret = 0;
308	struct lcd_platform_data *pd;
309	struct backlight_device *bd;
310
311	pd = lcd->lcd_pd;
312	bd = lcd->bd;
313
314	if (pd->power_on) {
315		pd->power_on(lcd->ld, 1);
316		msleep(pd->power_on_delay);
317	}
318
319	if (!pd->reset) {
320		dev_err(lcd->dev, "reset is NULL.\n");
321		return -EINVAL;
322	}
323
324	pd->reset(lcd->ld);
325	msleep(pd->reset_delay);
326
327	ret = ams369fg06_ldi_init(lcd);
328	if (ret) {
329		dev_err(lcd->dev, "failed to initialize ldi.\n");
330		return ret;
331	}
332
333	ret = ams369fg06_ldi_enable(lcd);
334	if (ret) {
335		dev_err(lcd->dev, "failed to enable ldi.\n");
336		return ret;
337	}
338
339	/* set brightness to current value after power on or resume. */
340	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
341	if (ret) {
342		dev_err(lcd->dev, "lcd gamma setting failed.\n");
343		return ret;
344	}
345
346	return 0;
347}
348
349static int ams369fg06_power_off(struct ams369fg06 *lcd)
350{
351	int ret;
352	struct lcd_platform_data *pd;
353
354	pd = lcd->lcd_pd;
355
356	ret = ams369fg06_ldi_disable(lcd);
357	if (ret) {
358		dev_err(lcd->dev, "lcd setting failed.\n");
359		return -EIO;
360	}
361
362	msleep(pd->power_off_delay);
363
364	if (pd->power_on)
365		pd->power_on(lcd->ld, 0);
366
367	return 0;
368}
369
370static int ams369fg06_power(struct ams369fg06 *lcd, int power)
371{
372	int ret = 0;
373
374	if (ams369fg06_power_is_on(power) &&
375		!ams369fg06_power_is_on(lcd->power))
376		ret = ams369fg06_power_on(lcd);
377	else if (!ams369fg06_power_is_on(power) &&
378		ams369fg06_power_is_on(lcd->power))
379		ret = ams369fg06_power_off(lcd);
380
381	if (!ret)
382		lcd->power = power;
383
384	return ret;
385}
386
387static int ams369fg06_get_power(struct lcd_device *ld)
388{
389	struct ams369fg06 *lcd = lcd_get_data(ld);
390
391	return lcd->power;
392}
393
394static int ams369fg06_set_power(struct lcd_device *ld, int power)
395{
396	struct ams369fg06 *lcd = lcd_get_data(ld);
397
398	if (power != BACKLIGHT_POWER_ON && power != BACKLIGHT_POWER_OFF &&
399		power != BACKLIGHT_POWER_REDUCED) {
400		dev_err(lcd->dev, "power value should be 0, 1 or 4.\n");
401		return -EINVAL;
402	}
403
404	return ams369fg06_power(lcd, power);
405}
406
407static int ams369fg06_set_brightness(struct backlight_device *bd)
408{
409	int ret = 0;
410	int brightness = bd->props.brightness;
411	struct ams369fg06 *lcd = bl_get_data(bd);
412
413	if (brightness < MIN_BRIGHTNESS ||
414		brightness > bd->props.max_brightness) {
415		dev_err(&bd->dev, "lcd brightness should be %d to %d.\n",
416			MIN_BRIGHTNESS, MAX_BRIGHTNESS);
417		return -EINVAL;
418	}
419
420	ret = ams369fg06_gamma_ctl(lcd, bd->props.brightness);
421	if (ret) {
422		dev_err(&bd->dev, "lcd brightness setting failed.\n");
423		return -EIO;
424	}
425
426	return ret;
427}
428
429static const struct lcd_ops ams369fg06_lcd_ops = {
430	.get_power = ams369fg06_get_power,
431	.set_power = ams369fg06_set_power,
432};
433
434static const struct backlight_ops ams369fg06_backlight_ops = {
435	.update_status = ams369fg06_set_brightness,
436};
437
438static int ams369fg06_probe(struct spi_device *spi)
439{
440	int ret = 0;
441	struct ams369fg06 *lcd = NULL;
442	struct lcd_device *ld = NULL;
443	struct backlight_device *bd = NULL;
444	struct backlight_properties props;
445
446	lcd = devm_kzalloc(&spi->dev, sizeof(struct ams369fg06), GFP_KERNEL);
447	if (!lcd)
448		return -ENOMEM;
449
450	/* ams369fg06 lcd panel uses 3-wire 16bits SPI Mode. */
451	spi->bits_per_word = 16;
452
453	ret = spi_setup(spi);
454	if (ret < 0) {
455		dev_err(&spi->dev, "spi setup failed.\n");
456		return ret;
457	}
458
459	lcd->spi = spi;
460	lcd->dev = &spi->dev;
461
462	lcd->lcd_pd = dev_get_platdata(&spi->dev);
463	if (!lcd->lcd_pd) {
464		dev_err(&spi->dev, "platform data is NULL\n");
465		return -EINVAL;
466	}
467
468	ld = devm_lcd_device_register(&spi->dev, "ams369fg06", &spi->dev, lcd,
469					&ams369fg06_lcd_ops);
470	if (IS_ERR(ld))
471		return PTR_ERR(ld);
472
473	lcd->ld = ld;
474
475	memset(&props, 0, sizeof(struct backlight_properties));
476	props.type = BACKLIGHT_RAW;
477	props.max_brightness = MAX_BRIGHTNESS;
478
479	bd = devm_backlight_device_register(&spi->dev, "ams369fg06-bl",
480					&spi->dev, lcd,
481					&ams369fg06_backlight_ops, &props);
482	if (IS_ERR(bd))
483		return PTR_ERR(bd);
484
485	bd->props.brightness = DEFAULT_BRIGHTNESS;
486	lcd->bd = bd;
487
488	if (!lcd->lcd_pd->lcd_enabled) {
489		/*
490		 * if lcd panel was off from bootloader then
491		 * current lcd status is powerdown and then
492		 * it enables lcd panel.
493		 */
494		lcd->power = BACKLIGHT_POWER_OFF;
495
496		ams369fg06_power(lcd, BACKLIGHT_POWER_ON);
497	} else {
498		lcd->power = BACKLIGHT_POWER_ON;
499	}
500
501	spi_set_drvdata(spi, lcd);
502
503	dev_info(&spi->dev, "ams369fg06 panel driver has been probed.\n");
504
505	return 0;
506}
507
508static void ams369fg06_remove(struct spi_device *spi)
509{
510	struct ams369fg06 *lcd = spi_get_drvdata(spi);
511
512	ams369fg06_power(lcd, BACKLIGHT_POWER_OFF);
513}
514
515#ifdef CONFIG_PM_SLEEP
516static int ams369fg06_suspend(struct device *dev)
517{
518	struct ams369fg06 *lcd = dev_get_drvdata(dev);
519
520	dev_dbg(dev, "lcd->power = %d\n", lcd->power);
521
522	/*
523	 * when lcd panel is suspend, lcd panel becomes off
524	 * regardless of status.
525	 */
526	return ams369fg06_power(lcd, BACKLIGHT_POWER_OFF);
527}
528
529static int ams369fg06_resume(struct device *dev)
530{
531	struct ams369fg06 *lcd = dev_get_drvdata(dev);
532
533	lcd->power = BACKLIGHT_POWER_OFF;
534
535	return ams369fg06_power(lcd, BACKLIGHT_POWER_ON);
536}
537#endif
538
539static SIMPLE_DEV_PM_OPS(ams369fg06_pm_ops, ams369fg06_suspend,
540			ams369fg06_resume);
541
542static void ams369fg06_shutdown(struct spi_device *spi)
543{
544	struct ams369fg06 *lcd = spi_get_drvdata(spi);
545
546	ams369fg06_power(lcd, BACKLIGHT_POWER_OFF);
547}
548
549static struct spi_driver ams369fg06_driver = {
550	.driver = {
551		.name	= "ams369fg06",
552		.pm	= &ams369fg06_pm_ops,
553	},
554	.probe		= ams369fg06_probe,
555	.remove		= ams369fg06_remove,
556	.shutdown	= ams369fg06_shutdown,
557};
558
559module_spi_driver(ams369fg06_driver);
560
561MODULE_AUTHOR("Jingoo Han <jg1.han@samsung.com>");
562MODULE_DESCRIPTION("ams369fg06 LCD Driver");
563MODULE_LICENSE("GPL");
~/ Read-only file preview.