1dc2e1e5bSLaurent Pinchart // SPDX-License-Identifier: GPL-2.0+
2dc2e1e5bSLaurent Pinchart /*
3dc2e1e5bSLaurent Pinchart  * Toppoly TD043MTEA1 Panel Driver
4dc2e1e5bSLaurent Pinchart  *
5dc2e1e5bSLaurent Pinchart  * Copyright (C) 2019 Texas Instruments Incorporated
6dc2e1e5bSLaurent Pinchart  *
7dc2e1e5bSLaurent Pinchart  * Based on the omapdrm-specific panel-tpo-td043mtea1 driver
8dc2e1e5bSLaurent Pinchart  *
9dc2e1e5bSLaurent Pinchart  * Author: Gražvydas Ignotas <notasas@gmail.com>
10dc2e1e5bSLaurent Pinchart  */
11dc2e1e5bSLaurent Pinchart 
12dc2e1e5bSLaurent Pinchart #include <linux/delay.h>
13dc2e1e5bSLaurent Pinchart #include <linux/module.h>
14dc2e1e5bSLaurent Pinchart #include <linux/regulator/consumer.h>
15dc2e1e5bSLaurent Pinchart #include <linux/spi/spi.h>
16dc2e1e5bSLaurent Pinchart 
17dc2e1e5bSLaurent Pinchart #include <drm/drm_connector.h>
18dc2e1e5bSLaurent Pinchart #include <drm/drm_modes.h>
19dc2e1e5bSLaurent Pinchart #include <drm/drm_panel.h>
20dc2e1e5bSLaurent Pinchart 
21dc2e1e5bSLaurent Pinchart #define TPO_R02_MODE(x)			((x) & 7)
22dc2e1e5bSLaurent Pinchart #define TPO_R02_MODE_800x480		7
23dc2e1e5bSLaurent Pinchart #define TPO_R02_NCLK_RISING		BIT(3)
24dc2e1e5bSLaurent Pinchart #define TPO_R02_HSYNC_HIGH		BIT(4)
25dc2e1e5bSLaurent Pinchart #define TPO_R02_VSYNC_HIGH		BIT(5)
26dc2e1e5bSLaurent Pinchart 
27dc2e1e5bSLaurent Pinchart #define TPO_R03_NSTANDBY		BIT(0)
28dc2e1e5bSLaurent Pinchart #define TPO_R03_EN_CP_CLK		BIT(1)
29dc2e1e5bSLaurent Pinchart #define TPO_R03_EN_VGL_PUMP		BIT(2)
30dc2e1e5bSLaurent Pinchart #define TPO_R03_EN_PWM			BIT(3)
31dc2e1e5bSLaurent Pinchart #define TPO_R03_DRIVING_CAP_100		BIT(4)
32dc2e1e5bSLaurent Pinchart #define TPO_R03_EN_PRE_CHARGE		BIT(6)
33dc2e1e5bSLaurent Pinchart #define TPO_R03_SOFTWARE_CTL		BIT(7)
34dc2e1e5bSLaurent Pinchart 
35dc2e1e5bSLaurent Pinchart #define TPO_R04_NFLIP_H			BIT(0)
36dc2e1e5bSLaurent Pinchart #define TPO_R04_NFLIP_V			BIT(1)
37dc2e1e5bSLaurent Pinchart #define TPO_R04_CP_CLK_FREQ_1H		BIT(2)
38dc2e1e5bSLaurent Pinchart #define TPO_R04_VGL_FREQ_1H		BIT(4)
39dc2e1e5bSLaurent Pinchart 
40dc2e1e5bSLaurent Pinchart #define TPO_R03_VAL_NORMAL \
41dc2e1e5bSLaurent Pinchart 	(TPO_R03_NSTANDBY | TPO_R03_EN_CP_CLK | TPO_R03_EN_VGL_PUMP | \
42dc2e1e5bSLaurent Pinchart 	 TPO_R03_EN_PWM | TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \
43dc2e1e5bSLaurent Pinchart 	 TPO_R03_SOFTWARE_CTL)
44dc2e1e5bSLaurent Pinchart 
45dc2e1e5bSLaurent Pinchart #define TPO_R03_VAL_STANDBY \
46dc2e1e5bSLaurent Pinchart 	(TPO_R03_DRIVING_CAP_100 | TPO_R03_EN_PRE_CHARGE | \
47dc2e1e5bSLaurent Pinchart 	 TPO_R03_SOFTWARE_CTL)
48dc2e1e5bSLaurent Pinchart 
49dc2e1e5bSLaurent Pinchart static const u16 td043mtea1_def_gamma[12] = {
50dc2e1e5bSLaurent Pinchart 	105, 315, 381, 431, 490, 537, 579, 686, 780, 837, 880, 1023
51dc2e1e5bSLaurent Pinchart };
52dc2e1e5bSLaurent Pinchart 
53dc2e1e5bSLaurent Pinchart struct td043mtea1_panel {
54dc2e1e5bSLaurent Pinchart 	struct drm_panel panel;
55dc2e1e5bSLaurent Pinchart 
56dc2e1e5bSLaurent Pinchart 	struct spi_device *spi;
57dc2e1e5bSLaurent Pinchart 	struct regulator *vcc_reg;
58dc2e1e5bSLaurent Pinchart 	struct gpio_desc *reset_gpio;
59dc2e1e5bSLaurent Pinchart 
60dc2e1e5bSLaurent Pinchart 	unsigned int mode;
61dc2e1e5bSLaurent Pinchart 	u16 gamma[12];
62dc2e1e5bSLaurent Pinchart 	bool vmirror;
63dc2e1e5bSLaurent Pinchart 	bool powered_on;
64dc2e1e5bSLaurent Pinchart 	bool spi_suspended;
65dc2e1e5bSLaurent Pinchart 	bool power_on_resume;
66dc2e1e5bSLaurent Pinchart };
67dc2e1e5bSLaurent Pinchart 
68dc2e1e5bSLaurent Pinchart #define to_td043mtea1_device(p) container_of(p, struct td043mtea1_panel, panel)
69dc2e1e5bSLaurent Pinchart 
70dc2e1e5bSLaurent Pinchart /* -----------------------------------------------------------------------------
71dc2e1e5bSLaurent Pinchart  * Hardware Access
72dc2e1e5bSLaurent Pinchart  */
73dc2e1e5bSLaurent Pinchart 
td043mtea1_write(struct td043mtea1_panel * lcd,u8 addr,u8 value)74dc2e1e5bSLaurent Pinchart static int td043mtea1_write(struct td043mtea1_panel *lcd, u8 addr, u8 value)
75dc2e1e5bSLaurent Pinchart {
76dc2e1e5bSLaurent Pinchart 	struct spi_message msg;
77dc2e1e5bSLaurent Pinchart 	struct spi_transfer xfer;
78dc2e1e5bSLaurent Pinchart 	u16 data;
79dc2e1e5bSLaurent Pinchart 	int ret;
80dc2e1e5bSLaurent Pinchart 
81dc2e1e5bSLaurent Pinchart 	spi_message_init(&msg);
82dc2e1e5bSLaurent Pinchart 
83dc2e1e5bSLaurent Pinchart 	memset(&xfer, 0, sizeof(xfer));
84dc2e1e5bSLaurent Pinchart 
85dc2e1e5bSLaurent Pinchart 	data = ((u16)addr << 10) | (1 << 8) | value;
86dc2e1e5bSLaurent Pinchart 	xfer.tx_buf = &data;
87dc2e1e5bSLaurent Pinchart 	xfer.bits_per_word = 16;
88dc2e1e5bSLaurent Pinchart 	xfer.len = 2;
89dc2e1e5bSLaurent Pinchart 	spi_message_add_tail(&xfer, &msg);
90dc2e1e5bSLaurent Pinchart 
91dc2e1e5bSLaurent Pinchart 	ret = spi_sync(lcd->spi, &msg);
92dc2e1e5bSLaurent Pinchart 	if (ret < 0)
93dc2e1e5bSLaurent Pinchart 		dev_warn(&lcd->spi->dev, "failed to write to LCD reg (%d)\n",
94dc2e1e5bSLaurent Pinchart 			 ret);
95dc2e1e5bSLaurent Pinchart 
96dc2e1e5bSLaurent Pinchart 	return ret;
97dc2e1e5bSLaurent Pinchart }
98dc2e1e5bSLaurent Pinchart 
td043mtea1_write_gamma(struct td043mtea1_panel * lcd)99dc2e1e5bSLaurent Pinchart static void td043mtea1_write_gamma(struct td043mtea1_panel *lcd)
100dc2e1e5bSLaurent Pinchart {
101dc2e1e5bSLaurent Pinchart 	const u16 *gamma = lcd->gamma;
102dc2e1e5bSLaurent Pinchart 	unsigned int i;
103dc2e1e5bSLaurent Pinchart 	u8 val;
104dc2e1e5bSLaurent Pinchart 
105dc2e1e5bSLaurent Pinchart 	/* gamma bits [9:8] */
106dc2e1e5bSLaurent Pinchart 	for (val = i = 0; i < 4; i++)
107dc2e1e5bSLaurent Pinchart 		val |= (gamma[i] & 0x300) >> ((i + 1) * 2);
108dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 0x11, val);
109dc2e1e5bSLaurent Pinchart 
110dc2e1e5bSLaurent Pinchart 	for (val = i = 0; i < 4; i++)
111dc2e1e5bSLaurent Pinchart 		val |= (gamma[i + 4] & 0x300) >> ((i + 1) * 2);
112dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 0x12, val);
113dc2e1e5bSLaurent Pinchart 
114dc2e1e5bSLaurent Pinchart 	for (val = i = 0; i < 4; i++)
115dc2e1e5bSLaurent Pinchart 		val |= (gamma[i + 8] & 0x300) >> ((i + 1) * 2);
116dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 0x13, val);
117dc2e1e5bSLaurent Pinchart 
118dc2e1e5bSLaurent Pinchart 	/* gamma bits [7:0] */
119b0baf85bSColin Ian King 	for (i = 0; i < 12; i++)
120dc2e1e5bSLaurent Pinchart 		td043mtea1_write(lcd, 0x14 + i, gamma[i] & 0xff);
121dc2e1e5bSLaurent Pinchart }
122dc2e1e5bSLaurent Pinchart 
td043mtea1_write_mirror(struct td043mtea1_panel * lcd)123dc2e1e5bSLaurent Pinchart static int td043mtea1_write_mirror(struct td043mtea1_panel *lcd)
124dc2e1e5bSLaurent Pinchart {
125dc2e1e5bSLaurent Pinchart 	u8 reg4 = TPO_R04_NFLIP_H | TPO_R04_NFLIP_V |
126dc2e1e5bSLaurent Pinchart 		TPO_R04_CP_CLK_FREQ_1H | TPO_R04_VGL_FREQ_1H;
127dc2e1e5bSLaurent Pinchart 	if (lcd->vmirror)
128dc2e1e5bSLaurent Pinchart 		reg4 &= ~TPO_R04_NFLIP_V;
129dc2e1e5bSLaurent Pinchart 
130dc2e1e5bSLaurent Pinchart 	return td043mtea1_write(lcd, 4, reg4);
131dc2e1e5bSLaurent Pinchart }
132dc2e1e5bSLaurent Pinchart 
td043mtea1_power_on(struct td043mtea1_panel * lcd)133dc2e1e5bSLaurent Pinchart static int td043mtea1_power_on(struct td043mtea1_panel *lcd)
134dc2e1e5bSLaurent Pinchart {
135dc2e1e5bSLaurent Pinchart 	int ret;
136dc2e1e5bSLaurent Pinchart 
137dc2e1e5bSLaurent Pinchart 	if (lcd->powered_on)
138dc2e1e5bSLaurent Pinchart 		return 0;
139dc2e1e5bSLaurent Pinchart 
140dc2e1e5bSLaurent Pinchart 	ret = regulator_enable(lcd->vcc_reg);
141dc2e1e5bSLaurent Pinchart 	if (ret < 0)
142dc2e1e5bSLaurent Pinchart 		return ret;
143dc2e1e5bSLaurent Pinchart 
144dc2e1e5bSLaurent Pinchart 	/* Wait for the panel to stabilize. */
145dc2e1e5bSLaurent Pinchart 	msleep(160);
146dc2e1e5bSLaurent Pinchart 
147dc2e1e5bSLaurent Pinchart 	gpiod_set_value(lcd->reset_gpio, 0);
148dc2e1e5bSLaurent Pinchart 
149dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 2, TPO_R02_MODE(lcd->mode) | TPO_R02_NCLK_RISING);
150dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 3, TPO_R03_VAL_NORMAL);
151dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 0x20, 0xf0);
152dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 0x21, 0xf0);
153dc2e1e5bSLaurent Pinchart 	td043mtea1_write_mirror(lcd);
154dc2e1e5bSLaurent Pinchart 	td043mtea1_write_gamma(lcd);
155dc2e1e5bSLaurent Pinchart 
156dc2e1e5bSLaurent Pinchart 	lcd->powered_on = true;
157dc2e1e5bSLaurent Pinchart 
158dc2e1e5bSLaurent Pinchart 	return 0;
159dc2e1e5bSLaurent Pinchart }
160dc2e1e5bSLaurent Pinchart 
td043mtea1_power_off(struct td043mtea1_panel * lcd)161dc2e1e5bSLaurent Pinchart static void td043mtea1_power_off(struct td043mtea1_panel *lcd)
162dc2e1e5bSLaurent Pinchart {
163dc2e1e5bSLaurent Pinchart 	if (!lcd->powered_on)
164dc2e1e5bSLaurent Pinchart 		return;
165dc2e1e5bSLaurent Pinchart 
166dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY | TPO_R03_EN_PWM);
167dc2e1e5bSLaurent Pinchart 
168dc2e1e5bSLaurent Pinchart 	gpiod_set_value(lcd->reset_gpio, 1);
169dc2e1e5bSLaurent Pinchart 
170dc2e1e5bSLaurent Pinchart 	/* wait for at least 2 vsyncs before cutting off power */
171dc2e1e5bSLaurent Pinchart 	msleep(50);
172dc2e1e5bSLaurent Pinchart 
173dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 3, TPO_R03_VAL_STANDBY);
174dc2e1e5bSLaurent Pinchart 
175dc2e1e5bSLaurent Pinchart 	regulator_disable(lcd->vcc_reg);
176dc2e1e5bSLaurent Pinchart 
177dc2e1e5bSLaurent Pinchart 	lcd->powered_on = false;
178dc2e1e5bSLaurent Pinchart }
179dc2e1e5bSLaurent Pinchart 
180dc2e1e5bSLaurent Pinchart /* -----------------------------------------------------------------------------
181dc2e1e5bSLaurent Pinchart  * sysfs
182dc2e1e5bSLaurent Pinchart  */
183dc2e1e5bSLaurent Pinchart 
vmirror_show(struct device * dev,struct device_attribute * attr,char * buf)184dc2e1e5bSLaurent Pinchart static ssize_t vmirror_show(struct device *dev, struct device_attribute *attr,
185dc2e1e5bSLaurent Pinchart 			    char *buf)
186dc2e1e5bSLaurent Pinchart {
187dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = dev_get_drvdata(dev);
188dc2e1e5bSLaurent Pinchart 
189e8b8b0dfSTian Tao 	return sysfs_emit(buf, "%d\n", lcd->vmirror);
190dc2e1e5bSLaurent Pinchart }
191dc2e1e5bSLaurent Pinchart 
vmirror_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)192dc2e1e5bSLaurent Pinchart static ssize_t vmirror_store(struct device *dev, struct device_attribute *attr,
193dc2e1e5bSLaurent Pinchart 			     const char *buf, size_t count)
194dc2e1e5bSLaurent Pinchart {
195dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = dev_get_drvdata(dev);
196dc2e1e5bSLaurent Pinchart 	int val;
197dc2e1e5bSLaurent Pinchart 	int ret;
198dc2e1e5bSLaurent Pinchart 
199dc2e1e5bSLaurent Pinchart 	ret = kstrtoint(buf, 0, &val);
200dc2e1e5bSLaurent Pinchart 	if (ret < 0)
201dc2e1e5bSLaurent Pinchart 		return ret;
202dc2e1e5bSLaurent Pinchart 
203dc2e1e5bSLaurent Pinchart 	lcd->vmirror = !!val;
204dc2e1e5bSLaurent Pinchart 
205dc2e1e5bSLaurent Pinchart 	ret = td043mtea1_write_mirror(lcd);
206dc2e1e5bSLaurent Pinchart 	if (ret < 0)
207dc2e1e5bSLaurent Pinchart 		return ret;
208dc2e1e5bSLaurent Pinchart 
209dc2e1e5bSLaurent Pinchart 	return count;
210dc2e1e5bSLaurent Pinchart }
211dc2e1e5bSLaurent Pinchart 
mode_show(struct device * dev,struct device_attribute * attr,char * buf)212dc2e1e5bSLaurent Pinchart static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
213dc2e1e5bSLaurent Pinchart 			 char *buf)
214dc2e1e5bSLaurent Pinchart {
215dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = dev_get_drvdata(dev);
216dc2e1e5bSLaurent Pinchart 
217e8b8b0dfSTian Tao 	return sysfs_emit(buf, "%d\n", lcd->mode);
218dc2e1e5bSLaurent Pinchart }
219dc2e1e5bSLaurent Pinchart 
mode_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)220dc2e1e5bSLaurent Pinchart static ssize_t mode_store(struct device *dev, struct device_attribute *attr,
221dc2e1e5bSLaurent Pinchart 			  const char *buf, size_t count)
222dc2e1e5bSLaurent Pinchart {
223dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = dev_get_drvdata(dev);
224dc2e1e5bSLaurent Pinchart 	long val;
225dc2e1e5bSLaurent Pinchart 	int ret;
226dc2e1e5bSLaurent Pinchart 
227dc2e1e5bSLaurent Pinchart 	ret = kstrtol(buf, 0, &val);
228dc2e1e5bSLaurent Pinchart 	if (ret != 0 || val & ~7)
229dc2e1e5bSLaurent Pinchart 		return -EINVAL;
230dc2e1e5bSLaurent Pinchart 
231dc2e1e5bSLaurent Pinchart 	lcd->mode = val;
232dc2e1e5bSLaurent Pinchart 
233dc2e1e5bSLaurent Pinchart 	val |= TPO_R02_NCLK_RISING;
234dc2e1e5bSLaurent Pinchart 	td043mtea1_write(lcd, 2, val);
235dc2e1e5bSLaurent Pinchart 
236dc2e1e5bSLaurent Pinchart 	return count;
237dc2e1e5bSLaurent Pinchart }
238dc2e1e5bSLaurent Pinchart 
gamma_show(struct device * dev,struct device_attribute * attr,char * buf)239dc2e1e5bSLaurent Pinchart static ssize_t gamma_show(struct device *dev, struct device_attribute *attr,
240dc2e1e5bSLaurent Pinchart 			  char *buf)
241dc2e1e5bSLaurent Pinchart {
242dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = dev_get_drvdata(dev);
243dc2e1e5bSLaurent Pinchart 	ssize_t len = 0;
244dc2e1e5bSLaurent Pinchart 	unsigned int i;
245dc2e1e5bSLaurent Pinchart 	int ret;
246dc2e1e5bSLaurent Pinchart 
247dc2e1e5bSLaurent Pinchart 	for (i = 0; i < ARRAY_SIZE(lcd->gamma); i++) {
248dc2e1e5bSLaurent Pinchart 		ret = snprintf(buf + len, PAGE_SIZE - len, "%u ",
249dc2e1e5bSLaurent Pinchart 			       lcd->gamma[i]);
250dc2e1e5bSLaurent Pinchart 		if (ret < 0)
251dc2e1e5bSLaurent Pinchart 			return ret;
252dc2e1e5bSLaurent Pinchart 		len += ret;
253dc2e1e5bSLaurent Pinchart 	}
254dc2e1e5bSLaurent Pinchart 	buf[len - 1] = '\n';
255dc2e1e5bSLaurent Pinchart 
256dc2e1e5bSLaurent Pinchart 	return len;
257dc2e1e5bSLaurent Pinchart }
258dc2e1e5bSLaurent Pinchart 
gamma_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)259dc2e1e5bSLaurent Pinchart static ssize_t gamma_store(struct device *dev, struct device_attribute *attr,
260dc2e1e5bSLaurent Pinchart 			   const char *buf, size_t count)
261dc2e1e5bSLaurent Pinchart {
262dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = dev_get_drvdata(dev);
263dc2e1e5bSLaurent Pinchart 	unsigned int g[12];
264dc2e1e5bSLaurent Pinchart 	unsigned int i;
265dc2e1e5bSLaurent Pinchart 	int ret;
266dc2e1e5bSLaurent Pinchart 
267dc2e1e5bSLaurent Pinchart 	ret = sscanf(buf, "%u %u %u %u %u %u %u %u %u %u %u %u",
268dc2e1e5bSLaurent Pinchart 		     &g[0], &g[1], &g[2], &g[3], &g[4], &g[5],
269dc2e1e5bSLaurent Pinchart 		     &g[6], &g[7], &g[8], &g[9], &g[10], &g[11]);
270dc2e1e5bSLaurent Pinchart 	if (ret != 12)
271dc2e1e5bSLaurent Pinchart 		return -EINVAL;
272dc2e1e5bSLaurent Pinchart 
273dc2e1e5bSLaurent Pinchart 	for (i = 0; i < 12; i++)
274dc2e1e5bSLaurent Pinchart 		lcd->gamma[i] = g[i];
275dc2e1e5bSLaurent Pinchart 
276dc2e1e5bSLaurent Pinchart 	td043mtea1_write_gamma(lcd);
277dc2e1e5bSLaurent Pinchart 
278dc2e1e5bSLaurent Pinchart 	return count;
279dc2e1e5bSLaurent Pinchart }
280dc2e1e5bSLaurent Pinchart 
281dc2e1e5bSLaurent Pinchart static DEVICE_ATTR_RW(vmirror);
282dc2e1e5bSLaurent Pinchart static DEVICE_ATTR_RW(mode);
283dc2e1e5bSLaurent Pinchart static DEVICE_ATTR_RW(gamma);
284dc2e1e5bSLaurent Pinchart 
285dc2e1e5bSLaurent Pinchart static struct attribute *td043mtea1_attrs[] = {
286dc2e1e5bSLaurent Pinchart 	&dev_attr_vmirror.attr,
287dc2e1e5bSLaurent Pinchart 	&dev_attr_mode.attr,
288dc2e1e5bSLaurent Pinchart 	&dev_attr_gamma.attr,
289dc2e1e5bSLaurent Pinchart 	NULL,
290dc2e1e5bSLaurent Pinchart };
291dc2e1e5bSLaurent Pinchart 
292dc2e1e5bSLaurent Pinchart static const struct attribute_group td043mtea1_attr_group = {
293dc2e1e5bSLaurent Pinchart 	.attrs = td043mtea1_attrs,
294dc2e1e5bSLaurent Pinchart };
295dc2e1e5bSLaurent Pinchart 
296dc2e1e5bSLaurent Pinchart /* -----------------------------------------------------------------------------
297dc2e1e5bSLaurent Pinchart  * Panel Operations
298dc2e1e5bSLaurent Pinchart  */
299dc2e1e5bSLaurent Pinchart 
td043mtea1_unprepare(struct drm_panel * panel)300dc2e1e5bSLaurent Pinchart static int td043mtea1_unprepare(struct drm_panel *panel)
301dc2e1e5bSLaurent Pinchart {
302dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = to_td043mtea1_device(panel);
303dc2e1e5bSLaurent Pinchart 
304dc2e1e5bSLaurent Pinchart 	if (!lcd->spi_suspended)
305dc2e1e5bSLaurent Pinchart 		td043mtea1_power_off(lcd);
306dc2e1e5bSLaurent Pinchart 
307dc2e1e5bSLaurent Pinchart 	return 0;
308dc2e1e5bSLaurent Pinchart }
309dc2e1e5bSLaurent Pinchart 
td043mtea1_prepare(struct drm_panel * panel)310dc2e1e5bSLaurent Pinchart static int td043mtea1_prepare(struct drm_panel *panel)
311dc2e1e5bSLaurent Pinchart {
312dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = to_td043mtea1_device(panel);
313dc2e1e5bSLaurent Pinchart 	int ret;
314dc2e1e5bSLaurent Pinchart 
315dc2e1e5bSLaurent Pinchart 	/*
316dc2e1e5bSLaurent Pinchart 	 * If we are resuming from system suspend, SPI might not be enabled
317dc2e1e5bSLaurent Pinchart 	 * yet, so we'll program the LCD from SPI PM resume callback.
318dc2e1e5bSLaurent Pinchart 	 */
319dc2e1e5bSLaurent Pinchart 	if (lcd->spi_suspended)
320dc2e1e5bSLaurent Pinchart 		return 0;
321dc2e1e5bSLaurent Pinchart 
322dc2e1e5bSLaurent Pinchart 	ret = td043mtea1_power_on(lcd);
323dc2e1e5bSLaurent Pinchart 	if (ret) {
324dc2e1e5bSLaurent Pinchart 		dev_err(&lcd->spi->dev, "%s: power on failed (%d)\n",
325dc2e1e5bSLaurent Pinchart 			__func__, ret);
326dc2e1e5bSLaurent Pinchart 		return ret;
327dc2e1e5bSLaurent Pinchart 	}
328dc2e1e5bSLaurent Pinchart 
329dc2e1e5bSLaurent Pinchart 	return 0;
330dc2e1e5bSLaurent Pinchart }
331dc2e1e5bSLaurent Pinchart 
332dc2e1e5bSLaurent Pinchart static const struct drm_display_mode td043mtea1_mode = {
333dc2e1e5bSLaurent Pinchart 	.clock = 36000,
334dc2e1e5bSLaurent Pinchart 	.hdisplay = 800,
335dc2e1e5bSLaurent Pinchart 	.hsync_start = 800 + 68,
336dc2e1e5bSLaurent Pinchart 	.hsync_end = 800 + 68 + 1,
337dc2e1e5bSLaurent Pinchart 	.htotal = 800 + 68 + 1 + 214,
338dc2e1e5bSLaurent Pinchart 	.vdisplay = 480,
339dc2e1e5bSLaurent Pinchart 	.vsync_start = 480 + 39,
340dc2e1e5bSLaurent Pinchart 	.vsync_end = 480 + 39 + 1,
341dc2e1e5bSLaurent Pinchart 	.vtotal = 480 + 39 + 1 + 34,
342dc2e1e5bSLaurent Pinchart 	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
343dc2e1e5bSLaurent Pinchart 	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
344dc2e1e5bSLaurent Pinchart 	.width_mm = 94,
345dc2e1e5bSLaurent Pinchart 	.height_mm = 56,
346dc2e1e5bSLaurent Pinchart };
347dc2e1e5bSLaurent Pinchart 
td043mtea1_get_modes(struct drm_panel * panel,struct drm_connector * connector)3480ce8ddd8SSam Ravnborg static int td043mtea1_get_modes(struct drm_panel *panel,
3490ce8ddd8SSam Ravnborg 				struct drm_connector *connector)
350dc2e1e5bSLaurent Pinchart {
351dc2e1e5bSLaurent Pinchart 	struct drm_display_mode *mode;
352dc2e1e5bSLaurent Pinchart 
353aa6c4364SSam Ravnborg 	mode = drm_mode_duplicate(connector->dev, &td043mtea1_mode);
354dc2e1e5bSLaurent Pinchart 	if (!mode)
355dc2e1e5bSLaurent Pinchart 		return -ENOMEM;
356dc2e1e5bSLaurent Pinchart 
357dc2e1e5bSLaurent Pinchart 	drm_mode_set_name(mode);
358dc2e1e5bSLaurent Pinchart 	drm_mode_probed_add(connector, mode);
359dc2e1e5bSLaurent Pinchart 
360dc2e1e5bSLaurent Pinchart 	connector->display_info.width_mm = td043mtea1_mode.width_mm;
361dc2e1e5bSLaurent Pinchart 	connector->display_info.height_mm = td043mtea1_mode.height_mm;
362dc2e1e5bSLaurent Pinchart 	/*
363dc2e1e5bSLaurent Pinchart 	 * FIXME: According to the datasheet sync signals are sampled on the
364dc2e1e5bSLaurent Pinchart 	 * rising edge of the clock, but the code running on the OMAP3 Pandora
365dc2e1e5bSLaurent Pinchart 	 * indicates sampling on the falling edge. This should be tested on a
366dc2e1e5bSLaurent Pinchart 	 * real device.
367dc2e1e5bSLaurent Pinchart 	 */
368dc2e1e5bSLaurent Pinchart 	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
369dc2e1e5bSLaurent Pinchart 					  | DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
370dc2e1e5bSLaurent Pinchart 					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE;
371dc2e1e5bSLaurent Pinchart 
372dc2e1e5bSLaurent Pinchart 	return 1;
373dc2e1e5bSLaurent Pinchart }
374dc2e1e5bSLaurent Pinchart 
375dc2e1e5bSLaurent Pinchart static const struct drm_panel_funcs td043mtea1_funcs = {
376dc2e1e5bSLaurent Pinchart 	.unprepare = td043mtea1_unprepare,
377dc2e1e5bSLaurent Pinchart 	.prepare = td043mtea1_prepare,
378dc2e1e5bSLaurent Pinchart 	.get_modes = td043mtea1_get_modes,
379dc2e1e5bSLaurent Pinchart };
380dc2e1e5bSLaurent Pinchart 
381dc2e1e5bSLaurent Pinchart /* -----------------------------------------------------------------------------
382dc2e1e5bSLaurent Pinchart  * Power Management, Probe and Remove
383dc2e1e5bSLaurent Pinchart  */
384dc2e1e5bSLaurent Pinchart 
td043mtea1_suspend(struct device * dev)385dc2e1e5bSLaurent Pinchart static int __maybe_unused td043mtea1_suspend(struct device *dev)
386dc2e1e5bSLaurent Pinchart {
387dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = dev_get_drvdata(dev);
388dc2e1e5bSLaurent Pinchart 
389dc2e1e5bSLaurent Pinchart 	if (lcd->powered_on) {
390dc2e1e5bSLaurent Pinchart 		td043mtea1_power_off(lcd);
391dc2e1e5bSLaurent Pinchart 		lcd->powered_on = true;
392dc2e1e5bSLaurent Pinchart 	}
393dc2e1e5bSLaurent Pinchart 
394dc2e1e5bSLaurent Pinchart 	lcd->spi_suspended = true;
395dc2e1e5bSLaurent Pinchart 
396dc2e1e5bSLaurent Pinchart 	return 0;
397dc2e1e5bSLaurent Pinchart }
398dc2e1e5bSLaurent Pinchart 
td043mtea1_resume(struct device * dev)399dc2e1e5bSLaurent Pinchart static int __maybe_unused td043mtea1_resume(struct device *dev)
400dc2e1e5bSLaurent Pinchart {
401dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = dev_get_drvdata(dev);
402dc2e1e5bSLaurent Pinchart 	int ret;
403dc2e1e5bSLaurent Pinchart 
404dc2e1e5bSLaurent Pinchart 	lcd->spi_suspended = false;
405dc2e1e5bSLaurent Pinchart 
406dc2e1e5bSLaurent Pinchart 	if (lcd->powered_on) {
407dc2e1e5bSLaurent Pinchart 		lcd->powered_on = false;
408dc2e1e5bSLaurent Pinchart 		ret = td043mtea1_power_on(lcd);
409dc2e1e5bSLaurent Pinchart 		if (ret)
410dc2e1e5bSLaurent Pinchart 			return ret;
411dc2e1e5bSLaurent Pinchart 	}
412dc2e1e5bSLaurent Pinchart 
413dc2e1e5bSLaurent Pinchart 	return 0;
414dc2e1e5bSLaurent Pinchart }
415dc2e1e5bSLaurent Pinchart 
416dc2e1e5bSLaurent Pinchart static SIMPLE_DEV_PM_OPS(td043mtea1_pm_ops, td043mtea1_suspend,
417dc2e1e5bSLaurent Pinchart 			 td043mtea1_resume);
418dc2e1e5bSLaurent Pinchart 
td043mtea1_probe(struct spi_device * spi)419dc2e1e5bSLaurent Pinchart static int td043mtea1_probe(struct spi_device *spi)
420dc2e1e5bSLaurent Pinchart {
421dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd;
422dc2e1e5bSLaurent Pinchart 	int ret;
423dc2e1e5bSLaurent Pinchart 
424dc2e1e5bSLaurent Pinchart 	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
425dc2e1e5bSLaurent Pinchart 	if (lcd == NULL)
426dc2e1e5bSLaurent Pinchart 		return -ENOMEM;
427dc2e1e5bSLaurent Pinchart 
428dc2e1e5bSLaurent Pinchart 	spi_set_drvdata(spi, lcd);
429dc2e1e5bSLaurent Pinchart 	lcd->spi = spi;
430dc2e1e5bSLaurent Pinchart 	lcd->mode = TPO_R02_MODE_800x480;
431dc2e1e5bSLaurent Pinchart 	memcpy(lcd->gamma, td043mtea1_def_gamma, sizeof(lcd->gamma));
432dc2e1e5bSLaurent Pinchart 
433dc2e1e5bSLaurent Pinchart 	lcd->vcc_reg = devm_regulator_get(&spi->dev, "vcc");
434a8daf03fSCai Huoqing 	if (IS_ERR(lcd->vcc_reg))
435a8daf03fSCai Huoqing 		return dev_err_probe(&spi->dev, PTR_ERR(lcd->vcc_reg),
436a8daf03fSCai Huoqing 				     "failed to get VCC regulator\n");
437dc2e1e5bSLaurent Pinchart 
438dc2e1e5bSLaurent Pinchart 	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);
439a8daf03fSCai Huoqing 	if (IS_ERR(lcd->reset_gpio))
440a8daf03fSCai Huoqing 		return dev_err_probe(&spi->dev, PTR_ERR(lcd->reset_gpio),
441a8daf03fSCai Huoqing 				     "failed to get reset GPIO\n");
442dc2e1e5bSLaurent Pinchart 
443dc2e1e5bSLaurent Pinchart 	spi->bits_per_word = 16;
444dc2e1e5bSLaurent Pinchart 	spi->mode = SPI_MODE_0;
445dc2e1e5bSLaurent Pinchart 
446dc2e1e5bSLaurent Pinchart 	ret = spi_setup(spi);
447dc2e1e5bSLaurent Pinchart 	if (ret < 0) {
448dc2e1e5bSLaurent Pinchart 		dev_err(&spi->dev, "failed to setup SPI: %d\n", ret);
449dc2e1e5bSLaurent Pinchart 		return ret;
450dc2e1e5bSLaurent Pinchart 	}
451dc2e1e5bSLaurent Pinchart 
452dc2e1e5bSLaurent Pinchart 	ret = sysfs_create_group(&spi->dev.kobj, &td043mtea1_attr_group);
453dc2e1e5bSLaurent Pinchart 	if (ret < 0) {
454dc2e1e5bSLaurent Pinchart 		dev_err(&spi->dev, "failed to create sysfs files\n");
455dc2e1e5bSLaurent Pinchart 		return ret;
456dc2e1e5bSLaurent Pinchart 	}
457dc2e1e5bSLaurent Pinchart 
4589a2654c0SLaurent Pinchart 	drm_panel_init(&lcd->panel, &lcd->spi->dev, &td043mtea1_funcs,
4599a2654c0SLaurent Pinchart 		       DRM_MODE_CONNECTOR_DPI);
460dc2e1e5bSLaurent Pinchart 
461c3ee8c65SBernard Zhao 	drm_panel_add(&lcd->panel);
462dc2e1e5bSLaurent Pinchart 
463dc2e1e5bSLaurent Pinchart 	return 0;
464dc2e1e5bSLaurent Pinchart }
465dc2e1e5bSLaurent Pinchart 
td043mtea1_remove(struct spi_device * spi)466*a0386bbaSUwe Kleine-König static void td043mtea1_remove(struct spi_device *spi)
467dc2e1e5bSLaurent Pinchart {
468dc2e1e5bSLaurent Pinchart 	struct td043mtea1_panel *lcd = spi_get_drvdata(spi);
469dc2e1e5bSLaurent Pinchart 
470dc2e1e5bSLaurent Pinchart 	drm_panel_remove(&lcd->panel);
471dc2e1e5bSLaurent Pinchart 	drm_panel_disable(&lcd->panel);
472dc2e1e5bSLaurent Pinchart 	drm_panel_unprepare(&lcd->panel);
473dc2e1e5bSLaurent Pinchart 
474dc2e1e5bSLaurent Pinchart 	sysfs_remove_group(&spi->dev.kobj, &td043mtea1_attr_group);
475dc2e1e5bSLaurent Pinchart }
476dc2e1e5bSLaurent Pinchart 
477dc2e1e5bSLaurent Pinchart static const struct of_device_id td043mtea1_of_match[] = {
478dc2e1e5bSLaurent Pinchart 	{ .compatible = "tpo,td043mtea1", },
479dc2e1e5bSLaurent Pinchart 	{ /* sentinel */ },
480dc2e1e5bSLaurent Pinchart };
481dc2e1e5bSLaurent Pinchart 
482dc2e1e5bSLaurent Pinchart MODULE_DEVICE_TABLE(of, td043mtea1_of_match);
483dc2e1e5bSLaurent Pinchart 
484cc635be3SLaurent Pinchart static const struct spi_device_id td043mtea1_ids[] = {
485cc635be3SLaurent Pinchart 	{ "td043mtea1", 0 },
486cc635be3SLaurent Pinchart 	{ /* sentinel */ }
487cc635be3SLaurent Pinchart };
488cc635be3SLaurent Pinchart 
489cc635be3SLaurent Pinchart MODULE_DEVICE_TABLE(spi, td043mtea1_ids);
490cc635be3SLaurent Pinchart 
491dc2e1e5bSLaurent Pinchart static struct spi_driver td043mtea1_driver = {
492dc2e1e5bSLaurent Pinchart 	.probe		= td043mtea1_probe,
493dc2e1e5bSLaurent Pinchart 	.remove		= td043mtea1_remove,
494cc635be3SLaurent Pinchart 	.id_table	= td043mtea1_ids,
495dc2e1e5bSLaurent Pinchart 	.driver		= {
496dc2e1e5bSLaurent Pinchart 		.name	= "panel-tpo-td043mtea1",
497dc2e1e5bSLaurent Pinchart 		.pm	= &td043mtea1_pm_ops,
498dc2e1e5bSLaurent Pinchart 		.of_match_table = td043mtea1_of_match,
499dc2e1e5bSLaurent Pinchart 	},
500dc2e1e5bSLaurent Pinchart };
501dc2e1e5bSLaurent Pinchart 
502dc2e1e5bSLaurent Pinchart module_spi_driver(td043mtea1_driver);
503dc2e1e5bSLaurent Pinchart 
504dc2e1e5bSLaurent Pinchart MODULE_AUTHOR("Gražvydas Ignotas <notasas@gmail.com>");
505dc2e1e5bSLaurent Pinchart MODULE_DESCRIPTION("TPO TD043MTEA1 Panel Driver");
506dc2e1e5bSLaurent Pinchart MODULE_LICENSE("GPL");
507