File Index
Files and directories
Sign in
FILE

ltv350qv.c

File preview with syntax highlighting.

linux-6.19.14/drivers/video/backlight/ltv350qv.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Power control for Samsung LTV350QV Quarter VGA LCD Panel
  4 *
  5 * Copyright (C) 2006, 2007 Atmel Corporation
  6 */
  7#include <linux/delay.h>
  8#include <linux/err.h>
  9#include <linux/init.h>
 10#include <linux/lcd.h>
 11#include <linux/module.h>
 12#include <linux/slab.h>
 13#include <linux/spi/spi.h>
 14
 15#include "ltv350qv.h"
 16
 17#define POWER_IS_ON(pwr)	((pwr) <= LCD_POWER_REDUCED)
 18
 19struct ltv350qv {
 20	struct spi_device	*spi;
 21	u8			*buffer;
 22	int			power;
 23	struct lcd_device	*ld;
 24};
 25
 26/*
 27 * The power-on and power-off sequences are taken from the
 28 * LTV350QV-F04 data sheet from Samsung. The register definitions are
 29 * taken from the S6F2002 command list also from Samsung.
 30 *
 31 * There's still some voodoo going on here, but it's a lot better than
 32 * in the first incarnation of the driver where all we had was the raw
 33 * numbers from the initialization sequence.
 34 */
 35static int ltv350qv_write_reg(struct ltv350qv *lcd, u8 reg, u16 val)
 36{
 37	struct spi_message msg;
 38	struct spi_transfer index_xfer = {
 39		.len		= 3,
 40		.cs_change	= 1,
 41	};
 42	struct spi_transfer value_xfer = {
 43		.len		= 3,
 44	};
 45
 46	spi_message_init(&msg);
 47
 48	/* register index */
 49	lcd->buffer[0] = LTV_OPC_INDEX;
 50	lcd->buffer[1] = 0x00;
 51	lcd->buffer[2] = reg & 0x7f;
 52	index_xfer.tx_buf = lcd->buffer;
 53	spi_message_add_tail(&index_xfer, &msg);
 54
 55	/* register value */
 56	lcd->buffer[4] = LTV_OPC_DATA;
 57	lcd->buffer[5] = val >> 8;
 58	lcd->buffer[6] = val;
 59	value_xfer.tx_buf = lcd->buffer + 4;
 60	spi_message_add_tail(&value_xfer, &msg);
 61
 62	return spi_sync(lcd->spi, &msg);
 63}
 64
 65/* The comments are taken straight from the data sheet */
 66static int ltv350qv_power_on(struct ltv350qv *lcd)
 67{
 68	int ret;
 69
 70	/* Power On Reset Display off State */
 71	if (ltv350qv_write_reg(lcd, LTV_PWRCTL1, 0x0000))
 72		goto err;
 73	usleep_range(15000, 16000);
 74
 75	/* Power Setting Function 1 */
 76	if (ltv350qv_write_reg(lcd, LTV_PWRCTL1, LTV_VCOM_DISABLE))
 77		goto err;
 78	if (ltv350qv_write_reg(lcd, LTV_PWRCTL2, LTV_VCOML_ENABLE))
 79		goto err_power1;
 80
 81	/* Power Setting Function 2 */
 82	if (ltv350qv_write_reg(lcd, LTV_PWRCTL1,
 83			       LTV_VCOM_DISABLE | LTV_DRIVE_CURRENT(5)
 84			       | LTV_SUPPLY_CURRENT(5)))
 85		goto err_power2;
 86
 87	msleep(55);
 88
 89	/* Instruction Setting */
 90	ret = ltv350qv_write_reg(lcd, LTV_IFCTL,
 91				 LTV_NMD | LTV_REV | LTV_NL(0x1d));
 92	ret |= ltv350qv_write_reg(lcd, LTV_DATACTL,
 93				  LTV_DS_SAME | LTV_CHS_480
 94				  | LTV_DF_RGB | LTV_RGB_BGR);
 95	ret |= ltv350qv_write_reg(lcd, LTV_ENTRY_MODE,
 96				  LTV_VSPL_ACTIVE_LOW
 97				  | LTV_HSPL_ACTIVE_LOW
 98				  | LTV_DPL_SAMPLE_RISING
 99				  | LTV_EPL_ACTIVE_LOW
100				  | LTV_SS_RIGHT_TO_LEFT);
101	ret |= ltv350qv_write_reg(lcd, LTV_GATECTL1, LTV_CLW(3));
102	ret |= ltv350qv_write_reg(lcd, LTV_GATECTL2,
103				  LTV_NW_INV_1LINE | LTV_FWI(3));
104	ret |= ltv350qv_write_reg(lcd, LTV_VBP, 0x000a);
105	ret |= ltv350qv_write_reg(lcd, LTV_HBP, 0x0021);
106	ret |= ltv350qv_write_reg(lcd, LTV_SOTCTL, LTV_SDT(3) | LTV_EQ(0));
107	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(0), 0x0103);
108	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(1), 0x0301);
109	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(2), 0x1f0f);
110	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(3), 0x1f0f);
111	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(4), 0x0707);
112	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(5), 0x0307);
113	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(6), 0x0707);
114	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(7), 0x0000);
115	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(8), 0x0004);
116	ret |= ltv350qv_write_reg(lcd, LTV_GAMMA(9), 0x0000);
117	if (ret)
118		goto err_settings;
119
120	/* Wait more than 2 frames */
121	msleep(20);
122
123	/* Display On Sequence */
124	ret = ltv350qv_write_reg(lcd, LTV_PWRCTL1,
125				 LTV_VCOM_DISABLE | LTV_VCOMOUT_ENABLE
126				 | LTV_POWER_ON | LTV_DRIVE_CURRENT(5)
127				 | LTV_SUPPLY_CURRENT(5));
128	ret |= ltv350qv_write_reg(lcd, LTV_GATECTL2,
129				  LTV_NW_INV_1LINE | LTV_DSC | LTV_FWI(3));
130	if (ret)
131		goto err_disp_on;
132
133	/* Display should now be ON. Phew. */
134	return 0;
135
136err_disp_on:
137	/*
138	 * Try to recover. Error handling probably isn't very useful
139	 * at this point, just make a best effort to switch the panel
140	 * off.
141	 */
142	ltv350qv_write_reg(lcd, LTV_PWRCTL1,
143			   LTV_VCOM_DISABLE | LTV_DRIVE_CURRENT(5)
144			   | LTV_SUPPLY_CURRENT(5));
145	ltv350qv_write_reg(lcd, LTV_GATECTL2,
146			   LTV_NW_INV_1LINE | LTV_FWI(3));
147err_settings:
148err_power2:
149err_power1:
150	ltv350qv_write_reg(lcd, LTV_PWRCTL2, 0x0000);
151	usleep_range(1000, 1100);
152err:
153	ltv350qv_write_reg(lcd, LTV_PWRCTL1, LTV_VCOM_DISABLE);
154	return -EIO;
155}
156
157static int ltv350qv_power_off(struct ltv350qv *lcd)
158{
159	int ret;
160
161	/* Display Off Sequence */
162	ret = ltv350qv_write_reg(lcd, LTV_PWRCTL1,
163				 LTV_VCOM_DISABLE
164				 | LTV_DRIVE_CURRENT(5)
165				 | LTV_SUPPLY_CURRENT(5));
166	ret |= ltv350qv_write_reg(lcd, LTV_GATECTL2,
167				  LTV_NW_INV_1LINE | LTV_FWI(3));
168
169	/* Power down setting 1 */
170	ret |= ltv350qv_write_reg(lcd, LTV_PWRCTL2, 0x0000);
171
172	/* Wait at least 1 ms */
173	usleep_range(1000, 1100);
174
175	/* Power down setting 2 */
176	ret |= ltv350qv_write_reg(lcd, LTV_PWRCTL1, LTV_VCOM_DISABLE);
177
178	/*
179	 * No point in trying to recover here. If we can't switch the
180	 * panel off, what are we supposed to do other than inform the
181	 * user about the failure?
182	 */
183	if (ret)
184		return -EIO;
185
186	/* Display power should now be OFF */
187	return 0;
188}
189
190static int ltv350qv_power(struct ltv350qv *lcd, int power)
191{
192	int ret = 0;
193
194	if (POWER_IS_ON(power) && !POWER_IS_ON(lcd->power))
195		ret = ltv350qv_power_on(lcd);
196	else if (!POWER_IS_ON(power) && POWER_IS_ON(lcd->power))
197		ret = ltv350qv_power_off(lcd);
198
199	if (!ret)
200		lcd->power = power;
201
202	return ret;
203}
204
205static int ltv350qv_set_power(struct lcd_device *ld, int power)
206{
207	struct ltv350qv *lcd = lcd_get_data(ld);
208
209	return ltv350qv_power(lcd, power);
210}
211
212static int ltv350qv_get_power(struct lcd_device *ld)
213{
214	struct ltv350qv *lcd = lcd_get_data(ld);
215
216	return lcd->power;
217}
218
219static const struct lcd_ops ltv_ops = {
220	.get_power	= ltv350qv_get_power,
221	.set_power	= ltv350qv_set_power,
222};
223
224static int ltv350qv_probe(struct spi_device *spi)
225{
226	struct ltv350qv *lcd;
227	struct lcd_device *ld;
228	int ret;
229
230	lcd = devm_kzalloc(&spi->dev, sizeof(struct ltv350qv), GFP_KERNEL);
231	if (!lcd)
232		return -ENOMEM;
233
234	lcd->spi = spi;
235	lcd->power = LCD_POWER_OFF;
236	lcd->buffer = devm_kzalloc(&spi->dev, 8, GFP_KERNEL);
237	if (!lcd->buffer)
238		return -ENOMEM;
239
240	ld = devm_lcd_device_register(&spi->dev, "ltv350qv", &spi->dev, lcd,
241					&ltv_ops);
242	if (IS_ERR(ld))
243		return PTR_ERR(ld);
244
245	lcd->ld = ld;
246
247	ret = ltv350qv_power(lcd, LCD_POWER_ON);
248	if (ret)
249		return ret;
250
251	spi_set_drvdata(spi, lcd);
252
253	return 0;
254}
255
256static void ltv350qv_remove(struct spi_device *spi)
257{
258	struct ltv350qv *lcd = spi_get_drvdata(spi);
259
260	ltv350qv_power(lcd, LCD_POWER_OFF);
261}
262
263#ifdef CONFIG_PM_SLEEP
264static int ltv350qv_suspend(struct device *dev)
265{
266	struct ltv350qv *lcd = dev_get_drvdata(dev);
267
268	return ltv350qv_power(lcd, LCD_POWER_OFF);
269}
270
271static int ltv350qv_resume(struct device *dev)
272{
273	struct ltv350qv *lcd = dev_get_drvdata(dev);
274
275	return ltv350qv_power(lcd, LCD_POWER_ON);
276}
277#endif
278
279static SIMPLE_DEV_PM_OPS(ltv350qv_pm_ops, ltv350qv_suspend, ltv350qv_resume);
280
281/* Power down all displays on reboot, poweroff or halt */
282static void ltv350qv_shutdown(struct spi_device *spi)
283{
284	struct ltv350qv *lcd = spi_get_drvdata(spi);
285
286	ltv350qv_power(lcd, LCD_POWER_OFF);
287}
288
289static struct spi_driver ltv350qv_driver = {
290	.driver = {
291		.name		= "ltv350qv",
292		.pm		= &ltv350qv_pm_ops,
293	},
294
295	.probe		= ltv350qv_probe,
296	.remove		= ltv350qv_remove,
297	.shutdown	= ltv350qv_shutdown,
298};
299
300module_spi_driver(ltv350qv_driver);
301
302MODULE_AUTHOR("Haavard Skinnemoen (Atmel)");
303MODULE_DESCRIPTION("Samsung LTV350QV LCD Driver");
304MODULE_LICENSE("GPL");
305MODULE_ALIAS("spi:ltv350qv");
~/ Read-only file preview.