18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * lms283gf05.c -- support for Samsung LMS283GF05 LCD
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2009 Marek Vasut <marek.vasut@gmail.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/device.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/delay.h>
118c2ecf20Sopenharmony_ci#include <linux/slab.h>
128c2ecf20Sopenharmony_ci#include <linux/gpio.h>
138c2ecf20Sopenharmony_ci#include <linux/lcd.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/spi/spi.h>
168c2ecf20Sopenharmony_ci#include <linux/spi/lms283gf05.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistruct lms283gf05_state {
208c2ecf20Sopenharmony_ci	struct spi_device	*spi;
218c2ecf20Sopenharmony_ci	struct lcd_device	*ld;
228c2ecf20Sopenharmony_ci};
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_cistruct lms283gf05_seq {
258c2ecf20Sopenharmony_ci	unsigned char		reg;
268c2ecf20Sopenharmony_ci	unsigned short		value;
278c2ecf20Sopenharmony_ci	unsigned char		delay;
288c2ecf20Sopenharmony_ci};
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci/* Magic sequences supplied by manufacturer, for details refer to datasheet */
318c2ecf20Sopenharmony_cistatic const struct lms283gf05_seq disp_initseq[] = {
328c2ecf20Sopenharmony_ci	/* REG, VALUE, DELAY */
338c2ecf20Sopenharmony_ci	{ 0x07, 0x0000, 0 },
348c2ecf20Sopenharmony_ci	{ 0x13, 0x0000, 10 },
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	{ 0x11, 0x3004, 0 },
378c2ecf20Sopenharmony_ci	{ 0x14, 0x200F, 0 },
388c2ecf20Sopenharmony_ci	{ 0x10, 0x1a20, 0 },
398c2ecf20Sopenharmony_ci	{ 0x13, 0x0040, 50 },
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	{ 0x13, 0x0060, 0 },
428c2ecf20Sopenharmony_ci	{ 0x13, 0x0070, 200 },
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	{ 0x01, 0x0127, 0 },
458c2ecf20Sopenharmony_ci	{ 0x02,	0x0700, 0 },
468c2ecf20Sopenharmony_ci	{ 0x03, 0x1030, 0 },
478c2ecf20Sopenharmony_ci	{ 0x08, 0x0208, 0 },
488c2ecf20Sopenharmony_ci	{ 0x0B, 0x0620, 0 },
498c2ecf20Sopenharmony_ci	{ 0x0C, 0x0110, 0 },
508c2ecf20Sopenharmony_ci	{ 0x30, 0x0120, 0 },
518c2ecf20Sopenharmony_ci	{ 0x31, 0x0127, 0 },
528c2ecf20Sopenharmony_ci	{ 0x32, 0x0000, 0 },
538c2ecf20Sopenharmony_ci	{ 0x33, 0x0503, 0 },
548c2ecf20Sopenharmony_ci	{ 0x34, 0x0727, 0 },
558c2ecf20Sopenharmony_ci	{ 0x35, 0x0124, 0 },
568c2ecf20Sopenharmony_ci	{ 0x36, 0x0706, 0 },
578c2ecf20Sopenharmony_ci	{ 0x37, 0x0701, 0 },
588c2ecf20Sopenharmony_ci	{ 0x38, 0x0F00, 0 },
598c2ecf20Sopenharmony_ci	{ 0x39, 0x0F00, 0 },
608c2ecf20Sopenharmony_ci	{ 0x40, 0x0000, 0 },
618c2ecf20Sopenharmony_ci	{ 0x41, 0x0000, 0 },
628c2ecf20Sopenharmony_ci	{ 0x42, 0x013f, 0 },
638c2ecf20Sopenharmony_ci	{ 0x43, 0x0000, 0 },
648c2ecf20Sopenharmony_ci	{ 0x44, 0x013f, 0 },
658c2ecf20Sopenharmony_ci	{ 0x45, 0x0000, 0 },
668c2ecf20Sopenharmony_ci	{ 0x46, 0xef00, 0 },
678c2ecf20Sopenharmony_ci	{ 0x47, 0x013f, 0 },
688c2ecf20Sopenharmony_ci	{ 0x48, 0x0000, 0 },
698c2ecf20Sopenharmony_ci	{ 0x07, 0x0015, 30 },
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	{ 0x07, 0x0017, 0 },
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	{ 0x20, 0x0000, 0 },
748c2ecf20Sopenharmony_ci	{ 0x21, 0x0000, 0 },
758c2ecf20Sopenharmony_ci	{ 0x22, 0x0000, 0 }
768c2ecf20Sopenharmony_ci};
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic const struct lms283gf05_seq disp_pdwnseq[] = {
798c2ecf20Sopenharmony_ci	{ 0x07, 0x0016, 30 },
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	{ 0x07, 0x0004, 0 },
828c2ecf20Sopenharmony_ci	{ 0x10, 0x0220, 20 },
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	{ 0x13, 0x0060, 50 },
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	{ 0x13, 0x0040, 50 },
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	{ 0x13, 0x0000, 0 },
898c2ecf20Sopenharmony_ci	{ 0x10, 0x0000, 0 }
908c2ecf20Sopenharmony_ci};
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic void lms283gf05_reset(unsigned long gpio, bool inverted)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	gpio_set_value(gpio, !inverted);
968c2ecf20Sopenharmony_ci	mdelay(100);
978c2ecf20Sopenharmony_ci	gpio_set_value(gpio, inverted);
988c2ecf20Sopenharmony_ci	mdelay(20);
998c2ecf20Sopenharmony_ci	gpio_set_value(gpio, !inverted);
1008c2ecf20Sopenharmony_ci	mdelay(20);
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic void lms283gf05_toggle(struct spi_device *spi,
1048c2ecf20Sopenharmony_ci				const struct lms283gf05_seq *seq, int sz)
1058c2ecf20Sopenharmony_ci{
1068c2ecf20Sopenharmony_ci	char buf[3];
1078c2ecf20Sopenharmony_ci	int i;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	for (i = 0; i < sz; i++) {
1108c2ecf20Sopenharmony_ci		buf[0] = 0x74;
1118c2ecf20Sopenharmony_ci		buf[1] = 0x00;
1128c2ecf20Sopenharmony_ci		buf[2] = seq[i].reg;
1138c2ecf20Sopenharmony_ci		spi_write(spi, buf, 3);
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci		buf[0] = 0x76;
1168c2ecf20Sopenharmony_ci		buf[1] = seq[i].value >> 8;
1178c2ecf20Sopenharmony_ci		buf[2] = seq[i].value & 0xff;
1188c2ecf20Sopenharmony_ci		spi_write(spi, buf, 3);
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci		mdelay(seq[i].delay);
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic int lms283gf05_power_set(struct lcd_device *ld, int power)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	struct lms283gf05_state *st = lcd_get_data(ld);
1278c2ecf20Sopenharmony_ci	struct spi_device *spi = st->spi;
1288c2ecf20Sopenharmony_ci	struct lms283gf05_pdata *pdata = dev_get_platdata(&spi->dev);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	if (power <= FB_BLANK_NORMAL) {
1318c2ecf20Sopenharmony_ci		if (pdata)
1328c2ecf20Sopenharmony_ci			lms283gf05_reset(pdata->reset_gpio,
1338c2ecf20Sopenharmony_ci					pdata->reset_inverted);
1348c2ecf20Sopenharmony_ci		lms283gf05_toggle(spi, disp_initseq, ARRAY_SIZE(disp_initseq));
1358c2ecf20Sopenharmony_ci	} else {
1368c2ecf20Sopenharmony_ci		lms283gf05_toggle(spi, disp_pdwnseq, ARRAY_SIZE(disp_pdwnseq));
1378c2ecf20Sopenharmony_ci		if (pdata)
1388c2ecf20Sopenharmony_ci			gpio_set_value(pdata->reset_gpio,
1398c2ecf20Sopenharmony_ci					pdata->reset_inverted);
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return 0;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic struct lcd_ops lms_ops = {
1468c2ecf20Sopenharmony_ci	.set_power	= lms283gf05_power_set,
1478c2ecf20Sopenharmony_ci	.get_power	= NULL,
1488c2ecf20Sopenharmony_ci};
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic int lms283gf05_probe(struct spi_device *spi)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	struct lms283gf05_state *st;
1538c2ecf20Sopenharmony_ci	struct lms283gf05_pdata *pdata = dev_get_platdata(&spi->dev);
1548c2ecf20Sopenharmony_ci	struct lcd_device *ld;
1558c2ecf20Sopenharmony_ci	int ret = 0;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	if (pdata != NULL) {
1588c2ecf20Sopenharmony_ci		ret = devm_gpio_request_one(&spi->dev, pdata->reset_gpio,
1598c2ecf20Sopenharmony_ci				GPIOF_DIR_OUT | (!pdata->reset_inverted ?
1608c2ecf20Sopenharmony_ci				GPIOF_INIT_HIGH : GPIOF_INIT_LOW),
1618c2ecf20Sopenharmony_ci				"LMS283GF05 RESET");
1628c2ecf20Sopenharmony_ci		if (ret)
1638c2ecf20Sopenharmony_ci			return ret;
1648c2ecf20Sopenharmony_ci	}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	st = devm_kzalloc(&spi->dev, sizeof(struct lms283gf05_state),
1678c2ecf20Sopenharmony_ci				GFP_KERNEL);
1688c2ecf20Sopenharmony_ci	if (st == NULL)
1698c2ecf20Sopenharmony_ci		return -ENOMEM;
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	ld = devm_lcd_device_register(&spi->dev, "lms283gf05", &spi->dev, st,
1728c2ecf20Sopenharmony_ci					&lms_ops);
1738c2ecf20Sopenharmony_ci	if (IS_ERR(ld))
1748c2ecf20Sopenharmony_ci		return PTR_ERR(ld);
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_ci	st->spi = spi;
1778c2ecf20Sopenharmony_ci	st->ld = ld;
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	spi_set_drvdata(spi, st);
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	/* kick in the LCD */
1828c2ecf20Sopenharmony_ci	if (pdata)
1838c2ecf20Sopenharmony_ci		lms283gf05_reset(pdata->reset_gpio, pdata->reset_inverted);
1848c2ecf20Sopenharmony_ci	lms283gf05_toggle(spi, disp_initseq, ARRAY_SIZE(disp_initseq));
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	return 0;
1878c2ecf20Sopenharmony_ci}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_cistatic struct spi_driver lms283gf05_driver = {
1908c2ecf20Sopenharmony_ci	.driver = {
1918c2ecf20Sopenharmony_ci		.name	= "lms283gf05",
1928c2ecf20Sopenharmony_ci	},
1938c2ecf20Sopenharmony_ci	.probe		= lms283gf05_probe,
1948c2ecf20Sopenharmony_ci};
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cimodule_spi_driver(lms283gf05_driver);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ciMODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
1998c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("LCD283GF05 LCD");
2008c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
201