1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 *  LCD/Backlight Driver for Sharp Zaurus Handhelds (various models)
4 *
5 *  Copyright (c) 2004-2006 Richard Purdie
6 *
7 *  Based on Sharp's 2.4 Backlight Driver
8 *
9 *  Copyright (c) 2008 Marvell International Ltd.
10 *	Converted to SPI device based LCD/Backlight device driver
11 *	by Eric Miao <eric.miao@marvell.com>
12 */
13
14#include <linux/backlight.h>
15#include <linux/module.h>
16#include <linux/kernel.h>
17#include <linux/init.h>
18#include <linux/delay.h>
19#include <linux/gpio/consumer.h>
20#include <linux/fb.h>
21#include <linux/lcd.h>
22#include <linux/spi/spi.h>
23#include <linux/spi/corgi_lcd.h>
24#include <linux/slab.h>
25#include <asm/mach/sharpsl_param.h>
26
27#define POWER_IS_ON(pwr)	((pwr) <= FB_BLANK_NORMAL)
28
29/* Register Addresses */
30#define RESCTL_ADRS     0x00
31#define PHACTRL_ADRS    0x01
32#define DUTYCTRL_ADRS   0x02
33#define POWERREG0_ADRS  0x03
34#define POWERREG1_ADRS  0x04
35#define GPOR3_ADRS      0x05
36#define PICTRL_ADRS     0x06
37#define POLCTRL_ADRS    0x07
38
39/* Register Bit Definitions */
40#define RESCTL_QVGA     0x01
41#define RESCTL_VGA      0x00
42
43#define POWER1_VW_ON    0x01  /* VW Supply FET ON */
44#define POWER1_GVSS_ON  0x02  /* GVSS(-8V) Power Supply ON */
45#define POWER1_VDD_ON   0x04  /* VDD(8V),SVSS(-4V) Power Supply ON */
46
47#define POWER1_VW_OFF   0x00  /* VW Supply FET OFF */
48#define POWER1_GVSS_OFF 0x00  /* GVSS(-8V) Power Supply OFF */
49#define POWER1_VDD_OFF  0x00  /* VDD(8V),SVSS(-4V) Power Supply OFF */
50
51#define POWER0_COM_DCLK 0x01  /* COM Voltage DC Bias DAC Serial Data Clock */
52#define POWER0_COM_DOUT 0x02  /* COM Voltage DC Bias DAC Serial Data Out */
53#define POWER0_DAC_ON   0x04  /* DAC Power Supply ON */
54#define POWER0_COM_ON   0x08  /* COM Power Supply ON */
55#define POWER0_VCC5_ON  0x10  /* VCC5 Power Supply ON */
56
57#define POWER0_DAC_OFF  0x00  /* DAC Power Supply OFF */
58#define POWER0_COM_OFF  0x00  /* COM Power Supply OFF */
59#define POWER0_VCC5_OFF 0x00  /* VCC5 Power Supply OFF */
60
61#define PICTRL_INIT_STATE      0x01
62#define PICTRL_INIOFF          0x02
63#define PICTRL_POWER_DOWN      0x04
64#define PICTRL_COM_SIGNAL_OFF  0x08
65#define PICTRL_DAC_SIGNAL_OFF  0x10
66
67#define POLCTRL_SYNC_POL_FALL  0x01
68#define POLCTRL_EN_POL_FALL    0x02
69#define POLCTRL_DATA_POL_FALL  0x04
70#define POLCTRL_SYNC_ACT_H     0x08
71#define POLCTRL_EN_ACT_L       0x10
72
73#define POLCTRL_SYNC_POL_RISE  0x00
74#define POLCTRL_EN_POL_RISE    0x00
75#define POLCTRL_DATA_POL_RISE  0x00
76#define POLCTRL_SYNC_ACT_L     0x00
77#define POLCTRL_EN_ACT_H       0x00
78
79#define PHACTRL_PHASE_MANUAL   0x01
80#define DEFAULT_PHAD_QVGA     (9)
81#define DEFAULT_COMADJ        (125)
82
83struct corgi_lcd {
84	struct spi_device	*spi_dev;
85	struct lcd_device	*lcd_dev;
86	struct backlight_device	*bl_dev;
87
88	int	limit_mask;
89	int	intensity;
90	int	power;
91	int	mode;
92	char	buf[2];
93
94	struct gpio_desc *backlight_on;
95	struct gpio_desc *backlight_cont;
96
97	void (*kick_battery)(void);
98};
99
100static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int reg, uint8_t val);
101
102static struct corgi_lcd *the_corgi_lcd;
103static unsigned long corgibl_flags;
104#define CORGIBL_SUSPENDED     0x01
105#define CORGIBL_BATTLOW       0x02
106
107/*
108 * This is only a pseudo I2C interface. We can't use the standard kernel
109 * routines as the interface is write only. We just assume the data is acked...
110 */
111static void lcdtg_ssp_i2c_send(struct corgi_lcd *lcd, uint8_t data)
112{
113	corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS, data);
114	udelay(10);
115}
116
117static void lcdtg_i2c_send_bit(struct corgi_lcd *lcd, uint8_t data)
118{
119	lcdtg_ssp_i2c_send(lcd, data);
120	lcdtg_ssp_i2c_send(lcd, data | POWER0_COM_DCLK);
121	lcdtg_ssp_i2c_send(lcd, data);
122}
123
124static void lcdtg_i2c_send_start(struct corgi_lcd *lcd, uint8_t base)
125{
126	lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK | POWER0_COM_DOUT);
127	lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK);
128	lcdtg_ssp_i2c_send(lcd, base);
129}
130
131static void lcdtg_i2c_send_stop(struct corgi_lcd *lcd, uint8_t base)
132{
133	lcdtg_ssp_i2c_send(lcd, base);
134	lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK);
135	lcdtg_ssp_i2c_send(lcd, base | POWER0_COM_DCLK | POWER0_COM_DOUT);
136}
137
138static void lcdtg_i2c_send_byte(struct corgi_lcd *lcd,
139				uint8_t base, uint8_t data)
140{
141	int i;
142
143	for (i = 0; i < 8; i++) {
144		if (data & 0x80)
145			lcdtg_i2c_send_bit(lcd, base | POWER0_COM_DOUT);
146		else
147			lcdtg_i2c_send_bit(lcd, base);
148		data <<= 1;
149	}
150}
151
152static void lcdtg_i2c_wait_ack(struct corgi_lcd *lcd, uint8_t base)
153{
154	lcdtg_i2c_send_bit(lcd, base);
155}
156
157static void lcdtg_set_common_voltage(struct corgi_lcd *lcd,
158				     uint8_t base_data, uint8_t data)
159{
160	/* Set Common Voltage to M62332FP via I2C */
161	lcdtg_i2c_send_start(lcd, base_data);
162	lcdtg_i2c_send_byte(lcd, base_data, 0x9c);
163	lcdtg_i2c_wait_ack(lcd, base_data);
164	lcdtg_i2c_send_byte(lcd, base_data, 0x00);
165	lcdtg_i2c_wait_ack(lcd, base_data);
166	lcdtg_i2c_send_byte(lcd, base_data, data);
167	lcdtg_i2c_wait_ack(lcd, base_data);
168	lcdtg_i2c_send_stop(lcd, base_data);
169}
170
171static int corgi_ssp_lcdtg_send(struct corgi_lcd *lcd, int adrs, uint8_t data)
172{
173	struct spi_message msg;
174	struct spi_transfer xfer = {
175		.len		= 1,
176		.cs_change	= 0,
177		.tx_buf		= lcd->buf,
178	};
179
180	lcd->buf[0] = ((adrs & 0x07) << 5) | (data & 0x1f);
181	spi_message_init(&msg);
182	spi_message_add_tail(&xfer, &msg);
183
184	return spi_sync(lcd->spi_dev, &msg);
185}
186
187/* Set Phase Adjust */
188static void lcdtg_set_phadadj(struct corgi_lcd *lcd, int mode)
189{
190	int adj;
191
192	switch (mode) {
193	case CORGI_LCD_MODE_VGA:
194		/* Setting for VGA */
195		adj = sharpsl_param.phadadj;
196		adj = (adj < 0) ? PHACTRL_PHASE_MANUAL :
197				  PHACTRL_PHASE_MANUAL | ((adj & 0xf) << 1);
198		break;
199	case CORGI_LCD_MODE_QVGA:
200	default:
201		/* Setting for QVGA */
202		adj = (DEFAULT_PHAD_QVGA << 1) | PHACTRL_PHASE_MANUAL;
203		break;
204	}
205
206	corgi_ssp_lcdtg_send(lcd, PHACTRL_ADRS, adj);
207}
208
209static void corgi_lcd_power_on(struct corgi_lcd *lcd)
210{
211	int comadj;
212
213	/* Initialize Internal Logic & Port */
214	corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS,
215			PICTRL_POWER_DOWN | PICTRL_INIOFF |
216			PICTRL_INIT_STATE | PICTRL_COM_SIGNAL_OFF |
217			PICTRL_DAC_SIGNAL_OFF);
218
219	corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
220			POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_OFF |
221			POWER0_COM_OFF | POWER0_VCC5_OFF);
222
223	corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
224			POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_OFF);
225
226	/* VDD(+8V), SVSS(-4V) ON */
227	corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
228			POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_ON);
229	mdelay(3);
230
231	/* DAC ON */
232	corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
233			POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON |
234			POWER0_COM_OFF | POWER0_VCC5_OFF);
235
236	/* INIB = H, INI = L  */
237	/* PICTL[0] = H , PICTL[1] = PICTL[2] = PICTL[4] = L */
238	corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS,
239			PICTRL_INIT_STATE | PICTRL_COM_SIGNAL_OFF);
240
241	/* Set Common Voltage */
242	comadj = sharpsl_param.comadj;
243	if (comadj < 0)
244		comadj = DEFAULT_COMADJ;
245
246	lcdtg_set_common_voltage(lcd, POWER0_DAC_ON | POWER0_COM_OFF |
247				 POWER0_VCC5_OFF, comadj);
248
249	/* VCC5 ON, DAC ON */
250	corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
251			POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON |
252			POWER0_COM_OFF | POWER0_VCC5_ON);
253
254	/* GVSS(-8V) ON, VDD ON */
255	corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
256			POWER1_VW_OFF | POWER1_GVSS_ON | POWER1_VDD_ON);
257	mdelay(2);
258
259	/* COM SIGNAL ON (PICTL[3] = L) */
260	corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, PICTRL_INIT_STATE);
261
262	/* COM ON, DAC ON, VCC5_ON */
263	corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
264			POWER0_COM_DCLK | POWER0_COM_DOUT | POWER0_DAC_ON |
265			POWER0_COM_ON | POWER0_VCC5_ON);
266
267	/* VW ON, GVSS ON, VDD ON */
268	corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
269			POWER1_VW_ON | POWER1_GVSS_ON | POWER1_VDD_ON);
270
271	/* Signals output enable */
272	corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, 0);
273
274	/* Set Phase Adjust */
275	lcdtg_set_phadadj(lcd, lcd->mode);
276
277	/* Initialize for Input Signals from ATI */
278	corgi_ssp_lcdtg_send(lcd, POLCTRL_ADRS,
279			POLCTRL_SYNC_POL_RISE | POLCTRL_EN_POL_RISE |
280			POLCTRL_DATA_POL_RISE | POLCTRL_SYNC_ACT_L |
281			POLCTRL_EN_ACT_H);
282	udelay(1000);
283
284	switch (lcd->mode) {
285	case CORGI_LCD_MODE_VGA:
286		corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_VGA);
287		break;
288	case CORGI_LCD_MODE_QVGA:
289	default:
290		corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_QVGA);
291		break;
292	}
293}
294
295static void corgi_lcd_power_off(struct corgi_lcd *lcd)
296{
297	/* 60Hz x 2 frame = 16.7msec x 2 = 33.4 msec */
298	msleep(34);
299
300	/* (1)VW OFF */
301	corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
302			POWER1_VW_OFF | POWER1_GVSS_ON | POWER1_VDD_ON);
303
304	/* (2)COM OFF */
305	corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS, PICTRL_COM_SIGNAL_OFF);
306	corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
307			POWER0_DAC_ON | POWER0_COM_OFF | POWER0_VCC5_ON);
308
309	/* (3)Set Common Voltage Bias 0V */
310	lcdtg_set_common_voltage(lcd, POWER0_DAC_ON | POWER0_COM_OFF |
311			POWER0_VCC5_ON, 0);
312
313	/* (4)GVSS OFF */
314	corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
315			POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_ON);
316
317	/* (5)VCC5 OFF */
318	corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
319			POWER0_DAC_ON | POWER0_COM_OFF | POWER0_VCC5_OFF);
320
321	/* (6)Set PDWN, INIOFF, DACOFF */
322	corgi_ssp_lcdtg_send(lcd, PICTRL_ADRS,
323			PICTRL_INIOFF | PICTRL_DAC_SIGNAL_OFF |
324			PICTRL_POWER_DOWN | PICTRL_COM_SIGNAL_OFF);
325
326	/* (7)DAC OFF */
327	corgi_ssp_lcdtg_send(lcd, POWERREG0_ADRS,
328			POWER0_DAC_OFF | POWER0_COM_OFF | POWER0_VCC5_OFF);
329
330	/* (8)VDD OFF */
331	corgi_ssp_lcdtg_send(lcd, POWERREG1_ADRS,
332			POWER1_VW_OFF | POWER1_GVSS_OFF | POWER1_VDD_OFF);
333}
334
335static int corgi_lcd_set_mode(struct lcd_device *ld, struct fb_videomode *m)
336{
337	struct corgi_lcd *lcd = lcd_get_data(ld);
338	int mode = CORGI_LCD_MODE_QVGA;
339
340	if (m->xres == 640 || m->xres == 480)
341		mode = CORGI_LCD_MODE_VGA;
342
343	if (lcd->mode == mode)
344		return 0;
345
346	lcdtg_set_phadadj(lcd, mode);
347
348	switch (mode) {
349	case CORGI_LCD_MODE_VGA:
350		corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_VGA);
351		break;
352	case CORGI_LCD_MODE_QVGA:
353	default:
354		corgi_ssp_lcdtg_send(lcd, RESCTL_ADRS, RESCTL_QVGA);
355		break;
356	}
357
358	lcd->mode = mode;
359	return 0;
360}
361
362static int corgi_lcd_set_power(struct lcd_device *ld, int power)
363{
364	struct corgi_lcd *lcd = lcd_get_data(ld);
365
366	if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
367		corgi_lcd_power_on(lcd);
368
369	if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
370		corgi_lcd_power_off(lcd);
371
372	lcd->power = power;
373	return 0;
374}
375
376static int corgi_lcd_get_power(struct lcd_device *ld)
377{
378	struct corgi_lcd *lcd = lcd_get_data(ld);
379
380	return lcd->power;
381}
382
383static struct lcd_ops corgi_lcd_ops = {
384	.get_power	= corgi_lcd_get_power,
385	.set_power	= corgi_lcd_set_power,
386	.set_mode	= corgi_lcd_set_mode,
387};
388
389static int corgi_bl_get_intensity(struct backlight_device *bd)
390{
391	struct corgi_lcd *lcd = bl_get_data(bd);
392
393	return lcd->intensity;
394}
395
396static int corgi_bl_set_intensity(struct corgi_lcd *lcd, int intensity)
397{
398	int cont;
399
400	if (intensity > 0x10)
401		intensity += 0x10;
402
403	corgi_ssp_lcdtg_send(lcd, DUTYCTRL_ADRS, intensity);
404
405	/* Bit 5 via GPIO_BACKLIGHT_CONT */
406	cont = !!(intensity & 0x20);
407
408	if (lcd->backlight_cont)
409		gpiod_set_value_cansleep(lcd->backlight_cont, cont);
410
411	if (lcd->backlight_on)
412		gpiod_set_value_cansleep(lcd->backlight_on, intensity);
413
414	if (lcd->kick_battery)
415		lcd->kick_battery();
416
417	lcd->intensity = intensity;
418	return 0;
419}
420
421static int corgi_bl_update_status(struct backlight_device *bd)
422{
423	struct corgi_lcd *lcd = bl_get_data(bd);
424	int intensity = backlight_get_brightness(bd);
425
426	if (corgibl_flags & CORGIBL_SUSPENDED)
427		intensity = 0;
428
429	if ((corgibl_flags & CORGIBL_BATTLOW) && intensity > lcd->limit_mask)
430		intensity = lcd->limit_mask;
431
432	return corgi_bl_set_intensity(lcd, intensity);
433}
434
435void corgi_lcd_limit_intensity(int limit)
436{
437	if (limit)
438		corgibl_flags |= CORGIBL_BATTLOW;
439	else
440		corgibl_flags &= ~CORGIBL_BATTLOW;
441
442	backlight_update_status(the_corgi_lcd->bl_dev);
443}
444EXPORT_SYMBOL(corgi_lcd_limit_intensity);
445
446static const struct backlight_ops corgi_bl_ops = {
447	.get_brightness	= corgi_bl_get_intensity,
448	.update_status  = corgi_bl_update_status,
449};
450
451#ifdef CONFIG_PM_SLEEP
452static int corgi_lcd_suspend(struct device *dev)
453{
454	struct corgi_lcd *lcd = dev_get_drvdata(dev);
455
456	corgibl_flags |= CORGIBL_SUSPENDED;
457	corgi_bl_set_intensity(lcd, 0);
458	corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN);
459	return 0;
460}
461
462static int corgi_lcd_resume(struct device *dev)
463{
464	struct corgi_lcd *lcd = dev_get_drvdata(dev);
465
466	corgibl_flags &= ~CORGIBL_SUSPENDED;
467	corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK);
468	backlight_update_status(lcd->bl_dev);
469	return 0;
470}
471#endif
472
473static SIMPLE_DEV_PM_OPS(corgi_lcd_pm_ops, corgi_lcd_suspend, corgi_lcd_resume);
474
475static int setup_gpio_backlight(struct corgi_lcd *lcd,
476				struct corgi_lcd_platform_data *pdata)
477{
478	struct spi_device *spi = lcd->spi_dev;
479
480	lcd->backlight_on = devm_gpiod_get_optional(&spi->dev,
481						    "BL_ON", GPIOD_OUT_LOW);
482	if (IS_ERR(lcd->backlight_on))
483		return PTR_ERR(lcd->backlight_on);
484
485	lcd->backlight_cont = devm_gpiod_get_optional(&spi->dev, "BL_CONT",
486						      GPIOD_OUT_LOW);
487	if (IS_ERR(lcd->backlight_cont))
488		return PTR_ERR(lcd->backlight_cont);
489
490	return 0;
491}
492
493static int corgi_lcd_probe(struct spi_device *spi)
494{
495	struct backlight_properties props;
496	struct corgi_lcd_platform_data *pdata = dev_get_platdata(&spi->dev);
497	struct corgi_lcd *lcd;
498	int ret = 0;
499
500	if (pdata == NULL) {
501		dev_err(&spi->dev, "platform data not available\n");
502		return -EINVAL;
503	}
504
505	lcd = devm_kzalloc(&spi->dev, sizeof(struct corgi_lcd), GFP_KERNEL);
506	if (!lcd)
507		return -ENOMEM;
508
509	lcd->spi_dev = spi;
510
511	lcd->lcd_dev = devm_lcd_device_register(&spi->dev, "corgi_lcd",
512						&spi->dev, lcd, &corgi_lcd_ops);
513	if (IS_ERR(lcd->lcd_dev))
514		return PTR_ERR(lcd->lcd_dev);
515
516	lcd->power = FB_BLANK_POWERDOWN;
517	lcd->mode = (pdata) ? pdata->init_mode : CORGI_LCD_MODE_VGA;
518
519	memset(&props, 0, sizeof(struct backlight_properties));
520	props.type = BACKLIGHT_RAW;
521	props.max_brightness = pdata->max_intensity;
522	lcd->bl_dev = devm_backlight_device_register(&spi->dev, "corgi_bl",
523						&spi->dev, lcd, &corgi_bl_ops,
524						&props);
525	if (IS_ERR(lcd->bl_dev))
526		return PTR_ERR(lcd->bl_dev);
527
528	lcd->bl_dev->props.brightness = pdata->default_intensity;
529	lcd->bl_dev->props.power = FB_BLANK_UNBLANK;
530
531	ret = setup_gpio_backlight(lcd, pdata);
532	if (ret)
533		return ret;
534
535	lcd->kick_battery = pdata->kick_battery;
536
537	spi_set_drvdata(spi, lcd);
538	corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_UNBLANK);
539	backlight_update_status(lcd->bl_dev);
540
541	lcd->limit_mask = pdata->limit_mask;
542	the_corgi_lcd = lcd;
543	return 0;
544}
545
546static void corgi_lcd_remove(struct spi_device *spi)
547{
548	struct corgi_lcd *lcd = spi_get_drvdata(spi);
549
550	lcd->bl_dev->props.power = FB_BLANK_UNBLANK;
551	lcd->bl_dev->props.brightness = 0;
552	backlight_update_status(lcd->bl_dev);
553	corgi_lcd_set_power(lcd->lcd_dev, FB_BLANK_POWERDOWN);
554}
555
556static struct spi_driver corgi_lcd_driver = {
557	.driver		= {
558		.name	= "corgi-lcd",
559		.pm	= &corgi_lcd_pm_ops,
560	},
561	.probe		= corgi_lcd_probe,
562	.remove		= corgi_lcd_remove,
563};
564
565module_spi_driver(corgi_lcd_driver);
566
567MODULE_DESCRIPTION("LCD and backlight driver for SHARP C7x0/Cxx00");
568MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
569MODULE_LICENSE("GPL");
570MODULE_ALIAS("spi:corgi-lcd");
571