18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/* drivers/video/backlight/ili9320.c
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * ILI9320 LCD controller driver core.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright 2007 Simtec Electronics
78c2ecf20Sopenharmony_ci *	http://armlinux.simtec.co.uk/
88c2ecf20Sopenharmony_ci *	Ben Dooks <ben@simtec.co.uk>
98c2ecf20Sopenharmony_ci*/
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/delay.h>
128c2ecf20Sopenharmony_ci#include <linux/err.h>
138c2ecf20Sopenharmony_ci#include <linux/fb.h>
148c2ecf20Sopenharmony_ci#include <linux/init.h>
158c2ecf20Sopenharmony_ci#include <linux/lcd.h>
168c2ecf20Sopenharmony_ci#include <linux/module.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#include <linux/spi/spi.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include <video/ili9320.h>
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#include "ili9320.h"
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic inline int ili9320_write_spi(struct ili9320 *ili,
278c2ecf20Sopenharmony_ci				    unsigned int reg,
288c2ecf20Sopenharmony_ci				    unsigned int value)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	struct ili9320_spi *spi = &ili->access.spi;
318c2ecf20Sopenharmony_ci	unsigned char *addr = spi->buffer_addr;
328c2ecf20Sopenharmony_ci	unsigned char *data = spi->buffer_data;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	/* spi message consits of:
358c2ecf20Sopenharmony_ci	 * first byte: ID and operation
368c2ecf20Sopenharmony_ci	 */
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	addr[0] = spi->id | ILI9320_SPI_INDEX | ILI9320_SPI_WRITE;
398c2ecf20Sopenharmony_ci	addr[1] = reg >> 8;
408c2ecf20Sopenharmony_ci	addr[2] = reg;
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	/* second message is the data to transfer */
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci	data[0] = spi->id | ILI9320_SPI_DATA  | ILI9320_SPI_WRITE;
458c2ecf20Sopenharmony_ci	data[1] = value >> 8;
468c2ecf20Sopenharmony_ci	data[2] = value;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	return spi_sync(spi->dev, &spi->message);
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ciint ili9320_write(struct ili9320 *ili, unsigned int reg, unsigned int value)
528c2ecf20Sopenharmony_ci{
538c2ecf20Sopenharmony_ci	dev_dbg(ili->dev, "write: reg=%02x, val=%04x\n", reg, value);
548c2ecf20Sopenharmony_ci	return ili->write(ili, reg, value);
558c2ecf20Sopenharmony_ci}
568c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ili9320_write);
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ciint ili9320_write_regs(struct ili9320 *ili,
598c2ecf20Sopenharmony_ci		       const struct ili9320_reg *values,
608c2ecf20Sopenharmony_ci		       int nr_values)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	int index;
638c2ecf20Sopenharmony_ci	int ret;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	for (index = 0; index < nr_values; index++, values++) {
668c2ecf20Sopenharmony_ci		ret = ili9320_write(ili, values->address, values->value);
678c2ecf20Sopenharmony_ci		if (ret != 0)
688c2ecf20Sopenharmony_ci			return ret;
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	return 0;
728c2ecf20Sopenharmony_ci}
738c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ili9320_write_regs);
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_cistatic void ili9320_reset(struct ili9320 *lcd)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	struct ili9320_platdata *cfg = lcd->platdata;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	cfg->reset(1);
808c2ecf20Sopenharmony_ci	mdelay(50);
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	cfg->reset(0);
838c2ecf20Sopenharmony_ci	mdelay(50);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	cfg->reset(1);
868c2ecf20Sopenharmony_ci	mdelay(100);
878c2ecf20Sopenharmony_ci}
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_cistatic inline int ili9320_init_chip(struct ili9320 *lcd)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	int ret;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	ili9320_reset(lcd);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	ret = lcd->client->init(lcd, lcd->platdata);
968c2ecf20Sopenharmony_ci	if (ret != 0) {
978c2ecf20Sopenharmony_ci		dev_err(lcd->dev, "failed to initialise display\n");
988c2ecf20Sopenharmony_ci		return ret;
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	lcd->initialised = 1;
1028c2ecf20Sopenharmony_ci	return 0;
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic inline int ili9320_power_on(struct ili9320 *lcd)
1068c2ecf20Sopenharmony_ci{
1078c2ecf20Sopenharmony_ci	if (!lcd->initialised)
1088c2ecf20Sopenharmony_ci		ili9320_init_chip(lcd);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	lcd->display1 |= (ILI9320_DISPLAY1_D(3) | ILI9320_DISPLAY1_BASEE);
1118c2ecf20Sopenharmony_ci	ili9320_write(lcd, ILI9320_DISPLAY1, lcd->display1);
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	return 0;
1148c2ecf20Sopenharmony_ci}
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic inline int ili9320_power_off(struct ili9320 *lcd)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	lcd->display1 &= ~(ILI9320_DISPLAY1_D(3) | ILI9320_DISPLAY1_BASEE);
1198c2ecf20Sopenharmony_ci	ili9320_write(lcd, ILI9320_DISPLAY1, lcd->display1);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	return 0;
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci#define POWER_IS_ON(pwr)	((pwr) <= FB_BLANK_NORMAL)
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_cistatic int ili9320_power(struct ili9320 *lcd, int power)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	int ret = 0;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	dev_dbg(lcd->dev, "power %d => %d\n", lcd->power, power);
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
1338c2ecf20Sopenharmony_ci		ret = ili9320_power_on(lcd);
1348c2ecf20Sopenharmony_ci	else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
1358c2ecf20Sopenharmony_ci		ret = ili9320_power_off(lcd);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	if (ret == 0)
1388c2ecf20Sopenharmony_ci		lcd->power = power;
1398c2ecf20Sopenharmony_ci	else
1408c2ecf20Sopenharmony_ci		dev_warn(lcd->dev, "failed to set power mode %d\n", power);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	return ret;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic inline struct ili9320 *to_our_lcd(struct lcd_device *lcd)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	return lcd_get_data(lcd);
1488c2ecf20Sopenharmony_ci}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_cistatic int ili9320_set_power(struct lcd_device *ld, int power)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	struct ili9320 *lcd = to_our_lcd(ld);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	return ili9320_power(lcd, power);
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic int ili9320_get_power(struct lcd_device *ld)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	struct ili9320 *lcd = to_our_lcd(ld);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	return lcd->power;
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic struct lcd_ops ili9320_ops = {
1658c2ecf20Sopenharmony_ci	.get_power	= ili9320_get_power,
1668c2ecf20Sopenharmony_ci	.set_power	= ili9320_set_power,
1678c2ecf20Sopenharmony_ci};
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_cistatic void ili9320_setup_spi(struct ili9320 *ili,
1708c2ecf20Sopenharmony_ci					struct spi_device *dev)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct ili9320_spi *spi = &ili->access.spi;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	ili->write = ili9320_write_spi;
1758c2ecf20Sopenharmony_ci	spi->dev = dev;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	/* fill the two messages we are going to use to send the data
1788c2ecf20Sopenharmony_ci	 * with, the first the address followed by the data. The datasheet
1798c2ecf20Sopenharmony_ci	 * says they should be done as two distinct cycles of the SPI CS line.
1808c2ecf20Sopenharmony_ci	 */
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	spi->xfer[0].tx_buf = spi->buffer_addr;
1838c2ecf20Sopenharmony_ci	spi->xfer[1].tx_buf = spi->buffer_data;
1848c2ecf20Sopenharmony_ci	spi->xfer[0].len = 3;
1858c2ecf20Sopenharmony_ci	spi->xfer[1].len = 3;
1868c2ecf20Sopenharmony_ci	spi->xfer[0].bits_per_word = 8;
1878c2ecf20Sopenharmony_ci	spi->xfer[1].bits_per_word = 8;
1888c2ecf20Sopenharmony_ci	spi->xfer[0].cs_change = 1;
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	spi_message_init(&spi->message);
1918c2ecf20Sopenharmony_ci	spi_message_add_tail(&spi->xfer[0], &spi->message);
1928c2ecf20Sopenharmony_ci	spi_message_add_tail(&spi->xfer[1], &spi->message);
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ciint ili9320_probe_spi(struct spi_device *spi,
1968c2ecf20Sopenharmony_ci				struct ili9320_client *client)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	struct ili9320_platdata *cfg = dev_get_platdata(&spi->dev);
1998c2ecf20Sopenharmony_ci	struct device *dev = &spi->dev;
2008c2ecf20Sopenharmony_ci	struct ili9320 *ili;
2018c2ecf20Sopenharmony_ci	struct lcd_device *lcd;
2028c2ecf20Sopenharmony_ci	int ret = 0;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	/* verify we where given some information */
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	if (cfg == NULL) {
2078c2ecf20Sopenharmony_ci		dev_err(dev, "no platform data supplied\n");
2088c2ecf20Sopenharmony_ci		return -EINVAL;
2098c2ecf20Sopenharmony_ci	}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	if (cfg->hsize <= 0 || cfg->vsize <= 0 || cfg->reset == NULL) {
2128c2ecf20Sopenharmony_ci		dev_err(dev, "invalid platform data supplied\n");
2138c2ecf20Sopenharmony_ci		return -EINVAL;
2148c2ecf20Sopenharmony_ci	}
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	/* allocate and initialse our state */
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	ili = devm_kzalloc(&spi->dev, sizeof(struct ili9320), GFP_KERNEL);
2198c2ecf20Sopenharmony_ci	if (ili == NULL)
2208c2ecf20Sopenharmony_ci		return -ENOMEM;
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	ili->access.spi.id = ILI9320_SPI_IDCODE | ILI9320_SPI_ID(1);
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	ili->dev = dev;
2258c2ecf20Sopenharmony_ci	ili->client = client;
2268c2ecf20Sopenharmony_ci	ili->power = FB_BLANK_POWERDOWN;
2278c2ecf20Sopenharmony_ci	ili->platdata = cfg;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	spi_set_drvdata(spi, ili);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	ili9320_setup_spi(ili, spi);
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	lcd = devm_lcd_device_register(&spi->dev, "ili9320", dev, ili,
2348c2ecf20Sopenharmony_ci					&ili9320_ops);
2358c2ecf20Sopenharmony_ci	if (IS_ERR(lcd)) {
2368c2ecf20Sopenharmony_ci		dev_err(dev, "failed to register lcd device\n");
2378c2ecf20Sopenharmony_ci		return PTR_ERR(lcd);
2388c2ecf20Sopenharmony_ci	}
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	ili->lcd = lcd;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	dev_info(dev, "initialising %s\n", client->name);
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	ret = ili9320_power(ili, FB_BLANK_UNBLANK);
2458c2ecf20Sopenharmony_ci	if (ret != 0) {
2468c2ecf20Sopenharmony_ci		dev_err(dev, "failed to set lcd power state\n");
2478c2ecf20Sopenharmony_ci		return ret;
2488c2ecf20Sopenharmony_ci	}
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	return 0;
2518c2ecf20Sopenharmony_ci}
2528c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ili9320_probe_spi);
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_ciint ili9320_remove(struct ili9320 *ili)
2558c2ecf20Sopenharmony_ci{
2568c2ecf20Sopenharmony_ci	ili9320_power(ili, FB_BLANK_POWERDOWN);
2578c2ecf20Sopenharmony_ci	return 0;
2588c2ecf20Sopenharmony_ci}
2598c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ili9320_remove);
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
2628c2ecf20Sopenharmony_ciint ili9320_suspend(struct ili9320 *lcd)
2638c2ecf20Sopenharmony_ci{
2648c2ecf20Sopenharmony_ci	int ret;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	ret = ili9320_power(lcd, FB_BLANK_POWERDOWN);
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	if (lcd->platdata->suspend == ILI9320_SUSPEND_DEEP) {
2698c2ecf20Sopenharmony_ci		ili9320_write(lcd, ILI9320_POWER1, lcd->power1 |
2708c2ecf20Sopenharmony_ci			      ILI9320_POWER1_SLP |
2718c2ecf20Sopenharmony_ci			      ILI9320_POWER1_DSTB);
2728c2ecf20Sopenharmony_ci		lcd->initialised = 0;
2738c2ecf20Sopenharmony_ci	}
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_ci	return ret;
2768c2ecf20Sopenharmony_ci}
2778c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ili9320_suspend);
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ciint ili9320_resume(struct ili9320 *lcd)
2808c2ecf20Sopenharmony_ci{
2818c2ecf20Sopenharmony_ci	dev_info(lcd->dev, "resuming from power state %d\n", lcd->power);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	if (lcd->platdata->suspend == ILI9320_SUSPEND_DEEP)
2848c2ecf20Sopenharmony_ci		ili9320_write(lcd, ILI9320_POWER1, 0x00);
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_ci	return ili9320_power(lcd, FB_BLANK_UNBLANK);
2878c2ecf20Sopenharmony_ci}
2888c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ili9320_resume);
2898c2ecf20Sopenharmony_ci#endif
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci/* Power down all displays on reboot, poweroff or halt */
2928c2ecf20Sopenharmony_civoid ili9320_shutdown(struct ili9320 *lcd)
2938c2ecf20Sopenharmony_ci{
2948c2ecf20Sopenharmony_ci	ili9320_power(lcd, FB_BLANK_POWERDOWN);
2958c2ecf20Sopenharmony_ci}
2968c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ili9320_shutdown);
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ben Dooks <ben-linux@fluff.org>");
2998c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ILI9320 LCD Driver");
3008c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
301