xref: /OK3568_Linux_fs/kernel/drivers/gpu/drm/panel/panel-sony-acx565akm.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * Sony ACX565AKM LCD Panel driver
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright (C) 2019 Texas Instruments Incorporated
6*4882a593Smuzhiyun  *
7*4882a593Smuzhiyun  * Based on the omapdrm-specific panel-sony-acx565akm driver
8*4882a593Smuzhiyun  *
9*4882a593Smuzhiyun  * Copyright (C) 2010 Nokia Corporation
10*4882a593Smuzhiyun  * Author: Imre Deak <imre.deak@nokia.com>
11*4882a593Smuzhiyun  */
12*4882a593Smuzhiyun 
13*4882a593Smuzhiyun /*
14*4882a593Smuzhiyun  * TODO (to be addressed with hardware access to test the changes):
15*4882a593Smuzhiyun  *
16*4882a593Smuzhiyun  * - Update backlight support to use backlight_update_status() etc.
17*4882a593Smuzhiyun  * - Use prepare/unprepare for the basic power on/off of the backligt
18*4882a593Smuzhiyun  */
19*4882a593Smuzhiyun 
20*4882a593Smuzhiyun #include <linux/backlight.h>
21*4882a593Smuzhiyun #include <linux/delay.h>
22*4882a593Smuzhiyun #include <linux/gpio/consumer.h>
23*4882a593Smuzhiyun #include <linux/jiffies.h>
24*4882a593Smuzhiyun #include <linux/module.h>
25*4882a593Smuzhiyun #include <linux/mutex.h>
26*4882a593Smuzhiyun #include <linux/sched.h>
27*4882a593Smuzhiyun #include <linux/spi/spi.h>
28*4882a593Smuzhiyun #include <video/mipi_display.h>
29*4882a593Smuzhiyun 
30*4882a593Smuzhiyun #include <drm/drm_connector.h>
31*4882a593Smuzhiyun #include <drm/drm_modes.h>
32*4882a593Smuzhiyun #include <drm/drm_panel.h>
33*4882a593Smuzhiyun 
34*4882a593Smuzhiyun #define CTRL_DISP_BRIGHTNESS_CTRL_ON		BIT(5)
35*4882a593Smuzhiyun #define CTRL_DISP_AMBIENT_LIGHT_CTRL_ON		BIT(4)
36*4882a593Smuzhiyun #define CTRL_DISP_BACKLIGHT_ON			BIT(2)
37*4882a593Smuzhiyun #define CTRL_DISP_AUTO_BRIGHTNESS_ON		BIT(1)
38*4882a593Smuzhiyun 
39*4882a593Smuzhiyun #define MIPID_CMD_WRITE_CABC		0x55
40*4882a593Smuzhiyun #define MIPID_CMD_READ_CABC		0x56
41*4882a593Smuzhiyun 
42*4882a593Smuzhiyun #define MIPID_VER_LPH8923		3
43*4882a593Smuzhiyun #define MIPID_VER_LS041Y3		4
44*4882a593Smuzhiyun #define MIPID_VER_L4F00311		8
45*4882a593Smuzhiyun #define MIPID_VER_ACX565AKM		9
46*4882a593Smuzhiyun 
47*4882a593Smuzhiyun struct acx565akm_panel {
48*4882a593Smuzhiyun 	struct drm_panel panel;
49*4882a593Smuzhiyun 
50*4882a593Smuzhiyun 	struct spi_device *spi;
51*4882a593Smuzhiyun 	struct gpio_desc *reset_gpio;
52*4882a593Smuzhiyun 	struct backlight_device *backlight;
53*4882a593Smuzhiyun 
54*4882a593Smuzhiyun 	struct mutex mutex;
55*4882a593Smuzhiyun 
56*4882a593Smuzhiyun 	const char *name;
57*4882a593Smuzhiyun 	u8 display_id[3];
58*4882a593Smuzhiyun 	int model;
59*4882a593Smuzhiyun 	int revision;
60*4882a593Smuzhiyun 	bool has_bc;
61*4882a593Smuzhiyun 	bool has_cabc;
62*4882a593Smuzhiyun 
63*4882a593Smuzhiyun 	bool enabled;
64*4882a593Smuzhiyun 	unsigned int cabc_mode;
65*4882a593Smuzhiyun 	/*
66*4882a593Smuzhiyun 	 * Next value of jiffies when we can issue the next sleep in/out
67*4882a593Smuzhiyun 	 * command.
68*4882a593Smuzhiyun 	 */
69*4882a593Smuzhiyun 	unsigned long hw_guard_end;
70*4882a593Smuzhiyun 	unsigned long hw_guard_wait;		/* max guard time in jiffies */
71*4882a593Smuzhiyun };
72*4882a593Smuzhiyun 
73*4882a593Smuzhiyun #define to_acx565akm_device(p) container_of(p, struct acx565akm_panel, panel)
74*4882a593Smuzhiyun 
acx565akm_transfer(struct acx565akm_panel * lcd,int cmd,const u8 * wbuf,int wlen,u8 * rbuf,int rlen)75*4882a593Smuzhiyun static void acx565akm_transfer(struct acx565akm_panel *lcd, int cmd,
76*4882a593Smuzhiyun 			      const u8 *wbuf, int wlen, u8 *rbuf, int rlen)
77*4882a593Smuzhiyun {
78*4882a593Smuzhiyun 	struct spi_message	m;
79*4882a593Smuzhiyun 	struct spi_transfer	*x, xfer[5];
80*4882a593Smuzhiyun 	int			ret;
81*4882a593Smuzhiyun 
82*4882a593Smuzhiyun 	spi_message_init(&m);
83*4882a593Smuzhiyun 
84*4882a593Smuzhiyun 	memset(xfer, 0, sizeof(xfer));
85*4882a593Smuzhiyun 	x = &xfer[0];
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 	cmd &=  0xff;
88*4882a593Smuzhiyun 	x->tx_buf = &cmd;
89*4882a593Smuzhiyun 	x->bits_per_word = 9;
90*4882a593Smuzhiyun 	x->len = 2;
91*4882a593Smuzhiyun 
92*4882a593Smuzhiyun 	if (rlen > 1 && wlen == 0) {
93*4882a593Smuzhiyun 		/*
94*4882a593Smuzhiyun 		 * Between the command and the response data there is a
95*4882a593Smuzhiyun 		 * dummy clock cycle. Add an extra bit after the command
96*4882a593Smuzhiyun 		 * word to account for this.
97*4882a593Smuzhiyun 		 */
98*4882a593Smuzhiyun 		x->bits_per_word = 10;
99*4882a593Smuzhiyun 		cmd <<= 1;
100*4882a593Smuzhiyun 	}
101*4882a593Smuzhiyun 	spi_message_add_tail(x, &m);
102*4882a593Smuzhiyun 
103*4882a593Smuzhiyun 	if (wlen) {
104*4882a593Smuzhiyun 		x++;
105*4882a593Smuzhiyun 		x->tx_buf = wbuf;
106*4882a593Smuzhiyun 		x->len = wlen;
107*4882a593Smuzhiyun 		x->bits_per_word = 9;
108*4882a593Smuzhiyun 		spi_message_add_tail(x, &m);
109*4882a593Smuzhiyun 	}
110*4882a593Smuzhiyun 
111*4882a593Smuzhiyun 	if (rlen) {
112*4882a593Smuzhiyun 		x++;
113*4882a593Smuzhiyun 		x->rx_buf	= rbuf;
114*4882a593Smuzhiyun 		x->len		= rlen;
115*4882a593Smuzhiyun 		spi_message_add_tail(x, &m);
116*4882a593Smuzhiyun 	}
117*4882a593Smuzhiyun 
118*4882a593Smuzhiyun 	ret = spi_sync(lcd->spi, &m);
119*4882a593Smuzhiyun 	if (ret < 0)
120*4882a593Smuzhiyun 		dev_dbg(&lcd->spi->dev, "spi_sync %d\n", ret);
121*4882a593Smuzhiyun }
122*4882a593Smuzhiyun 
acx565akm_cmd(struct acx565akm_panel * lcd,int cmd)123*4882a593Smuzhiyun static inline void acx565akm_cmd(struct acx565akm_panel *lcd, int cmd)
124*4882a593Smuzhiyun {
125*4882a593Smuzhiyun 	acx565akm_transfer(lcd, cmd, NULL, 0, NULL, 0);
126*4882a593Smuzhiyun }
127*4882a593Smuzhiyun 
acx565akm_write(struct acx565akm_panel * lcd,int reg,const u8 * buf,int len)128*4882a593Smuzhiyun static inline void acx565akm_write(struct acx565akm_panel *lcd,
129*4882a593Smuzhiyun 			       int reg, const u8 *buf, int len)
130*4882a593Smuzhiyun {
131*4882a593Smuzhiyun 	acx565akm_transfer(lcd, reg, buf, len, NULL, 0);
132*4882a593Smuzhiyun }
133*4882a593Smuzhiyun 
acx565akm_read(struct acx565akm_panel * lcd,int reg,u8 * buf,int len)134*4882a593Smuzhiyun static inline void acx565akm_read(struct acx565akm_panel *lcd,
135*4882a593Smuzhiyun 			      int reg, u8 *buf, int len)
136*4882a593Smuzhiyun {
137*4882a593Smuzhiyun 	acx565akm_transfer(lcd, reg, NULL, 0, buf, len);
138*4882a593Smuzhiyun }
139*4882a593Smuzhiyun 
140*4882a593Smuzhiyun /* -----------------------------------------------------------------------------
141*4882a593Smuzhiyun  * Auto Brightness Control Via sysfs
142*4882a593Smuzhiyun  */
143*4882a593Smuzhiyun 
acx565akm_get_cabc_mode(struct acx565akm_panel * lcd)144*4882a593Smuzhiyun static unsigned int acx565akm_get_cabc_mode(struct acx565akm_panel *lcd)
145*4882a593Smuzhiyun {
146*4882a593Smuzhiyun 	return lcd->cabc_mode;
147*4882a593Smuzhiyun }
148*4882a593Smuzhiyun 
acx565akm_set_cabc_mode(struct acx565akm_panel * lcd,unsigned int mode)149*4882a593Smuzhiyun static void acx565akm_set_cabc_mode(struct acx565akm_panel *lcd,
150*4882a593Smuzhiyun 				    unsigned int mode)
151*4882a593Smuzhiyun {
152*4882a593Smuzhiyun 	u16 cabc_ctrl;
153*4882a593Smuzhiyun 
154*4882a593Smuzhiyun 	lcd->cabc_mode = mode;
155*4882a593Smuzhiyun 	if (!lcd->enabled)
156*4882a593Smuzhiyun 		return;
157*4882a593Smuzhiyun 	cabc_ctrl = 0;
158*4882a593Smuzhiyun 	acx565akm_read(lcd, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1);
159*4882a593Smuzhiyun 	cabc_ctrl &= ~3;
160*4882a593Smuzhiyun 	cabc_ctrl |= (1 << 8) | (mode & 3);
161*4882a593Smuzhiyun 	acx565akm_write(lcd, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2);
162*4882a593Smuzhiyun }
163*4882a593Smuzhiyun 
acx565akm_get_hw_cabc_mode(struct acx565akm_panel * lcd)164*4882a593Smuzhiyun static unsigned int acx565akm_get_hw_cabc_mode(struct acx565akm_panel *lcd)
165*4882a593Smuzhiyun {
166*4882a593Smuzhiyun 	u8 cabc_ctrl;
167*4882a593Smuzhiyun 
168*4882a593Smuzhiyun 	acx565akm_read(lcd, MIPID_CMD_READ_CABC, &cabc_ctrl, 1);
169*4882a593Smuzhiyun 	return cabc_ctrl & 3;
170*4882a593Smuzhiyun }
171*4882a593Smuzhiyun 
172*4882a593Smuzhiyun static const char * const acx565akm_cabc_modes[] = {
173*4882a593Smuzhiyun 	"off",		/* always used when CABC is not supported */
174*4882a593Smuzhiyun 	"ui",
175*4882a593Smuzhiyun 	"still-image",
176*4882a593Smuzhiyun 	"moving-image",
177*4882a593Smuzhiyun };
178*4882a593Smuzhiyun 
cabc_mode_show(struct device * dev,struct device_attribute * attr,char * buf)179*4882a593Smuzhiyun static ssize_t cabc_mode_show(struct device *dev,
180*4882a593Smuzhiyun 			      struct device_attribute *attr,
181*4882a593Smuzhiyun 			      char *buf)
182*4882a593Smuzhiyun {
183*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = dev_get_drvdata(dev);
184*4882a593Smuzhiyun 	const char *mode_str;
185*4882a593Smuzhiyun 	int mode;
186*4882a593Smuzhiyun 
187*4882a593Smuzhiyun 	if (!lcd->has_cabc)
188*4882a593Smuzhiyun 		mode = 0;
189*4882a593Smuzhiyun 	else
190*4882a593Smuzhiyun 		mode = acx565akm_get_cabc_mode(lcd);
191*4882a593Smuzhiyun 
192*4882a593Smuzhiyun 	mode_str = "unknown";
193*4882a593Smuzhiyun 	if (mode >= 0 && mode < ARRAY_SIZE(acx565akm_cabc_modes))
194*4882a593Smuzhiyun 		mode_str = acx565akm_cabc_modes[mode];
195*4882a593Smuzhiyun 
196*4882a593Smuzhiyun 	return sprintf(buf, "%s\n", mode_str);
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun 
cabc_mode_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)199*4882a593Smuzhiyun static ssize_t cabc_mode_store(struct device *dev,
200*4882a593Smuzhiyun 			       struct device_attribute *attr,
201*4882a593Smuzhiyun 			       const char *buf, size_t count)
202*4882a593Smuzhiyun {
203*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = dev_get_drvdata(dev);
204*4882a593Smuzhiyun 	unsigned int i;
205*4882a593Smuzhiyun 
206*4882a593Smuzhiyun 	for (i = 0; i < ARRAY_SIZE(acx565akm_cabc_modes); i++) {
207*4882a593Smuzhiyun 		const char *mode_str = acx565akm_cabc_modes[i];
208*4882a593Smuzhiyun 		int cmp_len = strlen(mode_str);
209*4882a593Smuzhiyun 
210*4882a593Smuzhiyun 		if (count > 0 && buf[count - 1] == '\n')
211*4882a593Smuzhiyun 			count--;
212*4882a593Smuzhiyun 		if (count != cmp_len)
213*4882a593Smuzhiyun 			continue;
214*4882a593Smuzhiyun 
215*4882a593Smuzhiyun 		if (strncmp(buf, mode_str, cmp_len) == 0)
216*4882a593Smuzhiyun 			break;
217*4882a593Smuzhiyun 	}
218*4882a593Smuzhiyun 
219*4882a593Smuzhiyun 	if (i == ARRAY_SIZE(acx565akm_cabc_modes))
220*4882a593Smuzhiyun 		return -EINVAL;
221*4882a593Smuzhiyun 
222*4882a593Smuzhiyun 	if (!lcd->has_cabc && i != 0)
223*4882a593Smuzhiyun 		return -EINVAL;
224*4882a593Smuzhiyun 
225*4882a593Smuzhiyun 	mutex_lock(&lcd->mutex);
226*4882a593Smuzhiyun 	acx565akm_set_cabc_mode(lcd, i);
227*4882a593Smuzhiyun 	mutex_unlock(&lcd->mutex);
228*4882a593Smuzhiyun 
229*4882a593Smuzhiyun 	return count;
230*4882a593Smuzhiyun }
231*4882a593Smuzhiyun 
cabc_available_modes_show(struct device * dev,struct device_attribute * attr,char * buf)232*4882a593Smuzhiyun static ssize_t cabc_available_modes_show(struct device *dev,
233*4882a593Smuzhiyun 					 struct device_attribute *attr,
234*4882a593Smuzhiyun 					 char *buf)
235*4882a593Smuzhiyun {
236*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = dev_get_drvdata(dev);
237*4882a593Smuzhiyun 	unsigned int i;
238*4882a593Smuzhiyun 	size_t len = 0;
239*4882a593Smuzhiyun 
240*4882a593Smuzhiyun 	if (!lcd->has_cabc)
241*4882a593Smuzhiyun 		return sprintf(buf, "%s\n", acx565akm_cabc_modes[0]);
242*4882a593Smuzhiyun 
243*4882a593Smuzhiyun 	for (i = 0; i < ARRAY_SIZE(acx565akm_cabc_modes); i++)
244*4882a593Smuzhiyun 		len += sprintf(&buf[len], "%s%s", i ? " " : "",
245*4882a593Smuzhiyun 			       acx565akm_cabc_modes[i]);
246*4882a593Smuzhiyun 
247*4882a593Smuzhiyun 	buf[len++] = '\n';
248*4882a593Smuzhiyun 
249*4882a593Smuzhiyun 	return len;
250*4882a593Smuzhiyun }
251*4882a593Smuzhiyun 
252*4882a593Smuzhiyun static DEVICE_ATTR_RW(cabc_mode);
253*4882a593Smuzhiyun static DEVICE_ATTR_RO(cabc_available_modes);
254*4882a593Smuzhiyun 
255*4882a593Smuzhiyun static struct attribute *acx565akm_cabc_attrs[] = {
256*4882a593Smuzhiyun 	&dev_attr_cabc_mode.attr,
257*4882a593Smuzhiyun 	&dev_attr_cabc_available_modes.attr,
258*4882a593Smuzhiyun 	NULL,
259*4882a593Smuzhiyun };
260*4882a593Smuzhiyun 
261*4882a593Smuzhiyun static const struct attribute_group acx565akm_cabc_attr_group = {
262*4882a593Smuzhiyun 	.attrs = acx565akm_cabc_attrs,
263*4882a593Smuzhiyun };
264*4882a593Smuzhiyun 
265*4882a593Smuzhiyun /* -----------------------------------------------------------------------------
266*4882a593Smuzhiyun  * Backlight Device
267*4882a593Smuzhiyun  */
268*4882a593Smuzhiyun 
acx565akm_get_actual_brightness(struct acx565akm_panel * lcd)269*4882a593Smuzhiyun static int acx565akm_get_actual_brightness(struct acx565akm_panel *lcd)
270*4882a593Smuzhiyun {
271*4882a593Smuzhiyun 	u8 bv;
272*4882a593Smuzhiyun 
273*4882a593Smuzhiyun 	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_BRIGHTNESS, &bv, 1);
274*4882a593Smuzhiyun 
275*4882a593Smuzhiyun 	return bv;
276*4882a593Smuzhiyun }
277*4882a593Smuzhiyun 
acx565akm_set_brightness(struct acx565akm_panel * lcd,int level)278*4882a593Smuzhiyun static void acx565akm_set_brightness(struct acx565akm_panel *lcd, int level)
279*4882a593Smuzhiyun {
280*4882a593Smuzhiyun 	u16 ctrl;
281*4882a593Smuzhiyun 	int bv;
282*4882a593Smuzhiyun 
283*4882a593Smuzhiyun 	bv = level | (1 << 8);
284*4882a593Smuzhiyun 	acx565akm_write(lcd, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, (u8 *)&bv, 2);
285*4882a593Smuzhiyun 
286*4882a593Smuzhiyun 	acx565akm_read(lcd, MIPI_DCS_GET_CONTROL_DISPLAY, (u8 *)&ctrl, 1);
287*4882a593Smuzhiyun 	if (level)
288*4882a593Smuzhiyun 		ctrl |= CTRL_DISP_BRIGHTNESS_CTRL_ON |
289*4882a593Smuzhiyun 			CTRL_DISP_BACKLIGHT_ON;
290*4882a593Smuzhiyun 	else
291*4882a593Smuzhiyun 		ctrl &= ~(CTRL_DISP_BRIGHTNESS_CTRL_ON |
292*4882a593Smuzhiyun 			  CTRL_DISP_BACKLIGHT_ON);
293*4882a593Smuzhiyun 
294*4882a593Smuzhiyun 	ctrl |= 1 << 8;
295*4882a593Smuzhiyun 	acx565akm_write(lcd, MIPI_DCS_WRITE_CONTROL_DISPLAY, (u8 *)&ctrl, 2);
296*4882a593Smuzhiyun }
297*4882a593Smuzhiyun 
acx565akm_bl_update_status_locked(struct backlight_device * dev)298*4882a593Smuzhiyun static int acx565akm_bl_update_status_locked(struct backlight_device *dev)
299*4882a593Smuzhiyun {
300*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev);
301*4882a593Smuzhiyun 	int level;
302*4882a593Smuzhiyun 
303*4882a593Smuzhiyun 	if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
304*4882a593Smuzhiyun 	    dev->props.power == FB_BLANK_UNBLANK)
305*4882a593Smuzhiyun 		level = dev->props.brightness;
306*4882a593Smuzhiyun 	else
307*4882a593Smuzhiyun 		level = 0;
308*4882a593Smuzhiyun 
309*4882a593Smuzhiyun 	acx565akm_set_brightness(lcd, level);
310*4882a593Smuzhiyun 
311*4882a593Smuzhiyun 	return 0;
312*4882a593Smuzhiyun }
313*4882a593Smuzhiyun 
acx565akm_bl_update_status(struct backlight_device * dev)314*4882a593Smuzhiyun static int acx565akm_bl_update_status(struct backlight_device *dev)
315*4882a593Smuzhiyun {
316*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev);
317*4882a593Smuzhiyun 	int ret;
318*4882a593Smuzhiyun 
319*4882a593Smuzhiyun 	mutex_lock(&lcd->mutex);
320*4882a593Smuzhiyun 	ret = acx565akm_bl_update_status_locked(dev);
321*4882a593Smuzhiyun 	mutex_unlock(&lcd->mutex);
322*4882a593Smuzhiyun 
323*4882a593Smuzhiyun 	return ret;
324*4882a593Smuzhiyun }
325*4882a593Smuzhiyun 
acx565akm_bl_get_intensity(struct backlight_device * dev)326*4882a593Smuzhiyun static int acx565akm_bl_get_intensity(struct backlight_device *dev)
327*4882a593Smuzhiyun {
328*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = dev_get_drvdata(&dev->dev);
329*4882a593Smuzhiyun 	unsigned int intensity;
330*4882a593Smuzhiyun 
331*4882a593Smuzhiyun 	mutex_lock(&lcd->mutex);
332*4882a593Smuzhiyun 
333*4882a593Smuzhiyun 	if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
334*4882a593Smuzhiyun 	    dev->props.power == FB_BLANK_UNBLANK)
335*4882a593Smuzhiyun 		intensity = acx565akm_get_actual_brightness(lcd);
336*4882a593Smuzhiyun 	else
337*4882a593Smuzhiyun 		intensity = 0;
338*4882a593Smuzhiyun 
339*4882a593Smuzhiyun 	mutex_unlock(&lcd->mutex);
340*4882a593Smuzhiyun 
341*4882a593Smuzhiyun 	return intensity;
342*4882a593Smuzhiyun }
343*4882a593Smuzhiyun 
344*4882a593Smuzhiyun static const struct backlight_ops acx565akm_bl_ops = {
345*4882a593Smuzhiyun 	.get_brightness = acx565akm_bl_get_intensity,
346*4882a593Smuzhiyun 	.update_status  = acx565akm_bl_update_status,
347*4882a593Smuzhiyun };
348*4882a593Smuzhiyun 
acx565akm_backlight_init(struct acx565akm_panel * lcd)349*4882a593Smuzhiyun static int acx565akm_backlight_init(struct acx565akm_panel *lcd)
350*4882a593Smuzhiyun {
351*4882a593Smuzhiyun 	struct backlight_properties props = {
352*4882a593Smuzhiyun 		.fb_blank = FB_BLANK_UNBLANK,
353*4882a593Smuzhiyun 		.power = FB_BLANK_UNBLANK,
354*4882a593Smuzhiyun 		.type = BACKLIGHT_RAW,
355*4882a593Smuzhiyun 	};
356*4882a593Smuzhiyun 	int ret;
357*4882a593Smuzhiyun 
358*4882a593Smuzhiyun 	lcd->backlight = backlight_device_register(lcd->name, &lcd->spi->dev,
359*4882a593Smuzhiyun 						   lcd, &acx565akm_bl_ops,
360*4882a593Smuzhiyun 						   &props);
361*4882a593Smuzhiyun 	if (IS_ERR(lcd->backlight)) {
362*4882a593Smuzhiyun 		ret = PTR_ERR(lcd->backlight);
363*4882a593Smuzhiyun 		lcd->backlight = NULL;
364*4882a593Smuzhiyun 		return ret;
365*4882a593Smuzhiyun 	}
366*4882a593Smuzhiyun 
367*4882a593Smuzhiyun 	if (lcd->has_cabc) {
368*4882a593Smuzhiyun 		ret = sysfs_create_group(&lcd->backlight->dev.kobj,
369*4882a593Smuzhiyun 					 &acx565akm_cabc_attr_group);
370*4882a593Smuzhiyun 		if (ret < 0) {
371*4882a593Smuzhiyun 			dev_err(&lcd->spi->dev,
372*4882a593Smuzhiyun 				"%s failed to create sysfs files\n", __func__);
373*4882a593Smuzhiyun 			backlight_device_unregister(lcd->backlight);
374*4882a593Smuzhiyun 			return ret;
375*4882a593Smuzhiyun 		}
376*4882a593Smuzhiyun 
377*4882a593Smuzhiyun 		lcd->cabc_mode = acx565akm_get_hw_cabc_mode(lcd);
378*4882a593Smuzhiyun 	}
379*4882a593Smuzhiyun 
380*4882a593Smuzhiyun 	lcd->backlight->props.max_brightness = 255;
381*4882a593Smuzhiyun 	lcd->backlight->props.brightness = acx565akm_get_actual_brightness(lcd);
382*4882a593Smuzhiyun 
383*4882a593Smuzhiyun 	acx565akm_bl_update_status_locked(lcd->backlight);
384*4882a593Smuzhiyun 
385*4882a593Smuzhiyun 	return 0;
386*4882a593Smuzhiyun }
387*4882a593Smuzhiyun 
acx565akm_backlight_cleanup(struct acx565akm_panel * lcd)388*4882a593Smuzhiyun static void acx565akm_backlight_cleanup(struct acx565akm_panel *lcd)
389*4882a593Smuzhiyun {
390*4882a593Smuzhiyun 	if (lcd->has_cabc)
391*4882a593Smuzhiyun 		sysfs_remove_group(&lcd->backlight->dev.kobj,
392*4882a593Smuzhiyun 				   &acx565akm_cabc_attr_group);
393*4882a593Smuzhiyun 
394*4882a593Smuzhiyun 	backlight_device_unregister(lcd->backlight);
395*4882a593Smuzhiyun }
396*4882a593Smuzhiyun 
397*4882a593Smuzhiyun /* -----------------------------------------------------------------------------
398*4882a593Smuzhiyun  * DRM Bridge Operations
399*4882a593Smuzhiyun  */
400*4882a593Smuzhiyun 
acx565akm_set_sleep_mode(struct acx565akm_panel * lcd,int on)401*4882a593Smuzhiyun static void acx565akm_set_sleep_mode(struct acx565akm_panel *lcd, int on)
402*4882a593Smuzhiyun {
403*4882a593Smuzhiyun 	int cmd = on ? MIPI_DCS_ENTER_SLEEP_MODE : MIPI_DCS_EXIT_SLEEP_MODE;
404*4882a593Smuzhiyun 	unsigned long wait;
405*4882a593Smuzhiyun 
406*4882a593Smuzhiyun 	/*
407*4882a593Smuzhiyun 	 * We have to keep 120msec between sleep in/out commands.
408*4882a593Smuzhiyun 	 * (8.2.15, 8.2.16).
409*4882a593Smuzhiyun 	 */
410*4882a593Smuzhiyun 	wait = lcd->hw_guard_end - jiffies;
411*4882a593Smuzhiyun 	if ((long)wait > 0 && wait <= lcd->hw_guard_wait) {
412*4882a593Smuzhiyun 		set_current_state(TASK_UNINTERRUPTIBLE);
413*4882a593Smuzhiyun 		schedule_timeout(wait);
414*4882a593Smuzhiyun 	}
415*4882a593Smuzhiyun 
416*4882a593Smuzhiyun 	acx565akm_cmd(lcd, cmd);
417*4882a593Smuzhiyun 
418*4882a593Smuzhiyun 	lcd->hw_guard_wait = msecs_to_jiffies(120);
419*4882a593Smuzhiyun 	lcd->hw_guard_end = jiffies + lcd->hw_guard_wait;
420*4882a593Smuzhiyun }
421*4882a593Smuzhiyun 
acx565akm_set_display_state(struct acx565akm_panel * lcd,int enabled)422*4882a593Smuzhiyun static void acx565akm_set_display_state(struct acx565akm_panel *lcd,
423*4882a593Smuzhiyun 					int enabled)
424*4882a593Smuzhiyun {
425*4882a593Smuzhiyun 	int cmd = enabled ? MIPI_DCS_SET_DISPLAY_ON : MIPI_DCS_SET_DISPLAY_OFF;
426*4882a593Smuzhiyun 
427*4882a593Smuzhiyun 	acx565akm_cmd(lcd, cmd);
428*4882a593Smuzhiyun }
429*4882a593Smuzhiyun 
acx565akm_power_on(struct acx565akm_panel * lcd)430*4882a593Smuzhiyun static int acx565akm_power_on(struct acx565akm_panel *lcd)
431*4882a593Smuzhiyun {
432*4882a593Smuzhiyun 	/*FIXME tweak me */
433*4882a593Smuzhiyun 	msleep(50);
434*4882a593Smuzhiyun 
435*4882a593Smuzhiyun 	gpiod_set_value(lcd->reset_gpio, 1);
436*4882a593Smuzhiyun 
437*4882a593Smuzhiyun 	if (lcd->enabled) {
438*4882a593Smuzhiyun 		dev_dbg(&lcd->spi->dev, "panel already enabled\n");
439*4882a593Smuzhiyun 		return 0;
440*4882a593Smuzhiyun 	}
441*4882a593Smuzhiyun 
442*4882a593Smuzhiyun 	/*
443*4882a593Smuzhiyun 	 * We have to meet all the following delay requirements:
444*4882a593Smuzhiyun 	 * 1. tRW: reset pulse width 10usec (7.12.1)
445*4882a593Smuzhiyun 	 * 2. tRT: reset cancel time 5msec (7.12.1)
446*4882a593Smuzhiyun 	 * 3. Providing PCLK,HS,VS signals for 2 frames = ~50msec worst
447*4882a593Smuzhiyun 	 *    case (7.6.2)
448*4882a593Smuzhiyun 	 * 4. 120msec before the sleep out command (7.12.1)
449*4882a593Smuzhiyun 	 */
450*4882a593Smuzhiyun 	msleep(120);
451*4882a593Smuzhiyun 
452*4882a593Smuzhiyun 	acx565akm_set_sleep_mode(lcd, 0);
453*4882a593Smuzhiyun 	lcd->enabled = true;
454*4882a593Smuzhiyun 
455*4882a593Smuzhiyun 	/* 5msec between sleep out and the next command. (8.2.16) */
456*4882a593Smuzhiyun 	usleep_range(5000, 10000);
457*4882a593Smuzhiyun 	acx565akm_set_display_state(lcd, 1);
458*4882a593Smuzhiyun 	acx565akm_set_cabc_mode(lcd, lcd->cabc_mode);
459*4882a593Smuzhiyun 
460*4882a593Smuzhiyun 	return acx565akm_bl_update_status_locked(lcd->backlight);
461*4882a593Smuzhiyun }
462*4882a593Smuzhiyun 
acx565akm_power_off(struct acx565akm_panel * lcd)463*4882a593Smuzhiyun static void acx565akm_power_off(struct acx565akm_panel *lcd)
464*4882a593Smuzhiyun {
465*4882a593Smuzhiyun 	if (!lcd->enabled)
466*4882a593Smuzhiyun 		return;
467*4882a593Smuzhiyun 
468*4882a593Smuzhiyun 	acx565akm_set_display_state(lcd, 0);
469*4882a593Smuzhiyun 	acx565akm_set_sleep_mode(lcd, 1);
470*4882a593Smuzhiyun 	lcd->enabled = false;
471*4882a593Smuzhiyun 	/*
472*4882a593Smuzhiyun 	 * We have to provide PCLK,HS,VS signals for 2 frames (worst case
473*4882a593Smuzhiyun 	 * ~50msec) after sending the sleep in command and asserting the
474*4882a593Smuzhiyun 	 * reset signal. We probably could assert the reset w/o the delay
475*4882a593Smuzhiyun 	 * but we still delay to avoid possible artifacts. (7.6.1)
476*4882a593Smuzhiyun 	 */
477*4882a593Smuzhiyun 	msleep(50);
478*4882a593Smuzhiyun 
479*4882a593Smuzhiyun 	gpiod_set_value(lcd->reset_gpio, 0);
480*4882a593Smuzhiyun 
481*4882a593Smuzhiyun 	/* FIXME need to tweak this delay */
482*4882a593Smuzhiyun 	msleep(100);
483*4882a593Smuzhiyun }
484*4882a593Smuzhiyun 
acx565akm_disable(struct drm_panel * panel)485*4882a593Smuzhiyun static int acx565akm_disable(struct drm_panel *panel)
486*4882a593Smuzhiyun {
487*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = to_acx565akm_device(panel);
488*4882a593Smuzhiyun 
489*4882a593Smuzhiyun 	mutex_lock(&lcd->mutex);
490*4882a593Smuzhiyun 	acx565akm_power_off(lcd);
491*4882a593Smuzhiyun 	mutex_unlock(&lcd->mutex);
492*4882a593Smuzhiyun 
493*4882a593Smuzhiyun 	return 0;
494*4882a593Smuzhiyun }
495*4882a593Smuzhiyun 
acx565akm_enable(struct drm_panel * panel)496*4882a593Smuzhiyun static int acx565akm_enable(struct drm_panel *panel)
497*4882a593Smuzhiyun {
498*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = to_acx565akm_device(panel);
499*4882a593Smuzhiyun 
500*4882a593Smuzhiyun 	mutex_lock(&lcd->mutex);
501*4882a593Smuzhiyun 	acx565akm_power_on(lcd);
502*4882a593Smuzhiyun 	mutex_unlock(&lcd->mutex);
503*4882a593Smuzhiyun 
504*4882a593Smuzhiyun 	return 0;
505*4882a593Smuzhiyun }
506*4882a593Smuzhiyun 
507*4882a593Smuzhiyun static const struct drm_display_mode acx565akm_mode = {
508*4882a593Smuzhiyun 	.clock = 24000,
509*4882a593Smuzhiyun 	.hdisplay = 800,
510*4882a593Smuzhiyun 	.hsync_start = 800 + 28,
511*4882a593Smuzhiyun 	.hsync_end = 800 + 28 + 4,
512*4882a593Smuzhiyun 	.htotal = 800 + 28 + 4 + 24,
513*4882a593Smuzhiyun 	.vdisplay = 480,
514*4882a593Smuzhiyun 	.vsync_start = 480 + 3,
515*4882a593Smuzhiyun 	.vsync_end = 480 + 3 + 3,
516*4882a593Smuzhiyun 	.vtotal = 480 + 3 + 3 + 4,
517*4882a593Smuzhiyun 	.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
518*4882a593Smuzhiyun 	.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
519*4882a593Smuzhiyun 	.width_mm = 77,
520*4882a593Smuzhiyun 	.height_mm = 46,
521*4882a593Smuzhiyun };
522*4882a593Smuzhiyun 
acx565akm_get_modes(struct drm_panel * panel,struct drm_connector * connector)523*4882a593Smuzhiyun static int acx565akm_get_modes(struct drm_panel *panel,
524*4882a593Smuzhiyun 			       struct drm_connector *connector)
525*4882a593Smuzhiyun {
526*4882a593Smuzhiyun 	struct drm_display_mode *mode;
527*4882a593Smuzhiyun 
528*4882a593Smuzhiyun 	mode = drm_mode_duplicate(connector->dev, &acx565akm_mode);
529*4882a593Smuzhiyun 	if (!mode)
530*4882a593Smuzhiyun 		return -ENOMEM;
531*4882a593Smuzhiyun 
532*4882a593Smuzhiyun 	drm_mode_set_name(mode);
533*4882a593Smuzhiyun 	drm_mode_probed_add(connector, mode);
534*4882a593Smuzhiyun 
535*4882a593Smuzhiyun 	connector->display_info.width_mm = acx565akm_mode.width_mm;
536*4882a593Smuzhiyun 	connector->display_info.height_mm = acx565akm_mode.height_mm;
537*4882a593Smuzhiyun 	connector->display_info.bus_flags = DRM_BUS_FLAG_DE_HIGH
538*4882a593Smuzhiyun 					  | DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE
539*4882a593Smuzhiyun 					  | DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE;
540*4882a593Smuzhiyun 
541*4882a593Smuzhiyun 	return 1;
542*4882a593Smuzhiyun }
543*4882a593Smuzhiyun 
544*4882a593Smuzhiyun static const struct drm_panel_funcs acx565akm_funcs = {
545*4882a593Smuzhiyun 	.disable = acx565akm_disable,
546*4882a593Smuzhiyun 	.enable = acx565akm_enable,
547*4882a593Smuzhiyun 	.get_modes = acx565akm_get_modes,
548*4882a593Smuzhiyun };
549*4882a593Smuzhiyun 
550*4882a593Smuzhiyun /* -----------------------------------------------------------------------------
551*4882a593Smuzhiyun  * Probe, Detect and Remove
552*4882a593Smuzhiyun  */
553*4882a593Smuzhiyun 
acx565akm_detect(struct acx565akm_panel * lcd)554*4882a593Smuzhiyun static int acx565akm_detect(struct acx565akm_panel *lcd)
555*4882a593Smuzhiyun {
556*4882a593Smuzhiyun 	__be32 value;
557*4882a593Smuzhiyun 	u32 status;
558*4882a593Smuzhiyun 	int ret = 0;
559*4882a593Smuzhiyun 
560*4882a593Smuzhiyun 	/*
561*4882a593Smuzhiyun 	 * After being taken out of reset the panel needs 5ms before the first
562*4882a593Smuzhiyun 	 * command can be sent.
563*4882a593Smuzhiyun 	 */
564*4882a593Smuzhiyun 	gpiod_set_value(lcd->reset_gpio, 1);
565*4882a593Smuzhiyun 	usleep_range(5000, 10000);
566*4882a593Smuzhiyun 
567*4882a593Smuzhiyun 	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_STATUS, (u8 *)&value, 4);
568*4882a593Smuzhiyun 	status = __be32_to_cpu(value);
569*4882a593Smuzhiyun 	lcd->enabled = (status & (1 << 17)) && (status & (1 << 10));
570*4882a593Smuzhiyun 
571*4882a593Smuzhiyun 	dev_dbg(&lcd->spi->dev,
572*4882a593Smuzhiyun 		"LCD panel %s by bootloader (status 0x%04x)\n",
573*4882a593Smuzhiyun 		lcd->enabled ? "enabled" : "disabled ", status);
574*4882a593Smuzhiyun 
575*4882a593Smuzhiyun 	acx565akm_read(lcd, MIPI_DCS_GET_DISPLAY_ID, lcd->display_id, 3);
576*4882a593Smuzhiyun 	dev_dbg(&lcd->spi->dev, "MIPI display ID: %02x%02x%02x\n",
577*4882a593Smuzhiyun 		lcd->display_id[0], lcd->display_id[1], lcd->display_id[2]);
578*4882a593Smuzhiyun 
579*4882a593Smuzhiyun 	switch (lcd->display_id[0]) {
580*4882a593Smuzhiyun 	case 0x10:
581*4882a593Smuzhiyun 		lcd->model = MIPID_VER_ACX565AKM;
582*4882a593Smuzhiyun 		lcd->name = "acx565akm";
583*4882a593Smuzhiyun 		lcd->has_bc = 1;
584*4882a593Smuzhiyun 		lcd->has_cabc = 1;
585*4882a593Smuzhiyun 		break;
586*4882a593Smuzhiyun 	case 0x29:
587*4882a593Smuzhiyun 		lcd->model = MIPID_VER_L4F00311;
588*4882a593Smuzhiyun 		lcd->name = "l4f00311";
589*4882a593Smuzhiyun 		break;
590*4882a593Smuzhiyun 	case 0x45:
591*4882a593Smuzhiyun 		lcd->model = MIPID_VER_LPH8923;
592*4882a593Smuzhiyun 		lcd->name = "lph8923";
593*4882a593Smuzhiyun 		break;
594*4882a593Smuzhiyun 	case 0x83:
595*4882a593Smuzhiyun 		lcd->model = MIPID_VER_LS041Y3;
596*4882a593Smuzhiyun 		lcd->name = "ls041y3";
597*4882a593Smuzhiyun 		break;
598*4882a593Smuzhiyun 	default:
599*4882a593Smuzhiyun 		lcd->name = "unknown";
600*4882a593Smuzhiyun 		dev_err(&lcd->spi->dev, "unknown display ID\n");
601*4882a593Smuzhiyun 		ret = -ENODEV;
602*4882a593Smuzhiyun 		goto done;
603*4882a593Smuzhiyun 	}
604*4882a593Smuzhiyun 
605*4882a593Smuzhiyun 	lcd->revision = lcd->display_id[1];
606*4882a593Smuzhiyun 
607*4882a593Smuzhiyun 	dev_info(&lcd->spi->dev, "%s rev %02x panel detected\n",
608*4882a593Smuzhiyun 		 lcd->name, lcd->revision);
609*4882a593Smuzhiyun 
610*4882a593Smuzhiyun done:
611*4882a593Smuzhiyun 	if (!lcd->enabled)
612*4882a593Smuzhiyun 		gpiod_set_value(lcd->reset_gpio, 0);
613*4882a593Smuzhiyun 
614*4882a593Smuzhiyun 	return ret;
615*4882a593Smuzhiyun }
616*4882a593Smuzhiyun 
acx565akm_probe(struct spi_device * spi)617*4882a593Smuzhiyun static int acx565akm_probe(struct spi_device *spi)
618*4882a593Smuzhiyun {
619*4882a593Smuzhiyun 	struct acx565akm_panel *lcd;
620*4882a593Smuzhiyun 	int ret;
621*4882a593Smuzhiyun 
622*4882a593Smuzhiyun 	lcd = devm_kzalloc(&spi->dev, sizeof(*lcd), GFP_KERNEL);
623*4882a593Smuzhiyun 	if (!lcd)
624*4882a593Smuzhiyun 		return -ENOMEM;
625*4882a593Smuzhiyun 
626*4882a593Smuzhiyun 	spi_set_drvdata(spi, lcd);
627*4882a593Smuzhiyun 	spi->mode = SPI_MODE_3;
628*4882a593Smuzhiyun 
629*4882a593Smuzhiyun 	lcd->spi = spi;
630*4882a593Smuzhiyun 	mutex_init(&lcd->mutex);
631*4882a593Smuzhiyun 
632*4882a593Smuzhiyun 	lcd->reset_gpio = devm_gpiod_get(&spi->dev, "reset", GPIOD_OUT_HIGH);
633*4882a593Smuzhiyun 	if (IS_ERR(lcd->reset_gpio)) {
634*4882a593Smuzhiyun 		dev_err(&spi->dev, "failed to get reset GPIO\n");
635*4882a593Smuzhiyun 		return PTR_ERR(lcd->reset_gpio);
636*4882a593Smuzhiyun 	}
637*4882a593Smuzhiyun 
638*4882a593Smuzhiyun 	ret = acx565akm_detect(lcd);
639*4882a593Smuzhiyun 	if (ret < 0) {
640*4882a593Smuzhiyun 		dev_err(&spi->dev, "panel detection failed\n");
641*4882a593Smuzhiyun 		return ret;
642*4882a593Smuzhiyun 	}
643*4882a593Smuzhiyun 
644*4882a593Smuzhiyun 	if (lcd->has_bc) {
645*4882a593Smuzhiyun 		ret = acx565akm_backlight_init(lcd);
646*4882a593Smuzhiyun 		if (ret < 0)
647*4882a593Smuzhiyun 			return ret;
648*4882a593Smuzhiyun 	}
649*4882a593Smuzhiyun 
650*4882a593Smuzhiyun 	drm_panel_init(&lcd->panel, &lcd->spi->dev, &acx565akm_funcs,
651*4882a593Smuzhiyun 		       DRM_MODE_CONNECTOR_DPI);
652*4882a593Smuzhiyun 
653*4882a593Smuzhiyun 	drm_panel_add(&lcd->panel);
654*4882a593Smuzhiyun 
655*4882a593Smuzhiyun 	return 0;
656*4882a593Smuzhiyun }
657*4882a593Smuzhiyun 
acx565akm_remove(struct spi_device * spi)658*4882a593Smuzhiyun static int acx565akm_remove(struct spi_device *spi)
659*4882a593Smuzhiyun {
660*4882a593Smuzhiyun 	struct acx565akm_panel *lcd = spi_get_drvdata(spi);
661*4882a593Smuzhiyun 
662*4882a593Smuzhiyun 	drm_panel_remove(&lcd->panel);
663*4882a593Smuzhiyun 
664*4882a593Smuzhiyun 	if (lcd->has_bc)
665*4882a593Smuzhiyun 		acx565akm_backlight_cleanup(lcd);
666*4882a593Smuzhiyun 
667*4882a593Smuzhiyun 	drm_panel_disable(&lcd->panel);
668*4882a593Smuzhiyun 	drm_panel_unprepare(&lcd->panel);
669*4882a593Smuzhiyun 
670*4882a593Smuzhiyun 	return 0;
671*4882a593Smuzhiyun }
672*4882a593Smuzhiyun 
673*4882a593Smuzhiyun static const struct of_device_id acx565akm_of_match[] = {
674*4882a593Smuzhiyun 	{ .compatible = "sony,acx565akm", },
675*4882a593Smuzhiyun 	{ /* sentinel */ },
676*4882a593Smuzhiyun };
677*4882a593Smuzhiyun 
678*4882a593Smuzhiyun MODULE_DEVICE_TABLE(of, acx565akm_of_match);
679*4882a593Smuzhiyun 
680*4882a593Smuzhiyun static const struct spi_device_id acx565akm_ids[] = {
681*4882a593Smuzhiyun 	{ "acx565akm", 0 },
682*4882a593Smuzhiyun 	{ /* sentinel */ }
683*4882a593Smuzhiyun };
684*4882a593Smuzhiyun 
685*4882a593Smuzhiyun MODULE_DEVICE_TABLE(spi, acx565akm_ids);
686*4882a593Smuzhiyun 
687*4882a593Smuzhiyun static struct spi_driver acx565akm_driver = {
688*4882a593Smuzhiyun 	.probe		= acx565akm_probe,
689*4882a593Smuzhiyun 	.remove		= acx565akm_remove,
690*4882a593Smuzhiyun 	.id_table	= acx565akm_ids,
691*4882a593Smuzhiyun 	.driver		= {
692*4882a593Smuzhiyun 		.name	= "panel-sony-acx565akm",
693*4882a593Smuzhiyun 		.of_match_table = acx565akm_of_match,
694*4882a593Smuzhiyun 	},
695*4882a593Smuzhiyun };
696*4882a593Smuzhiyun 
697*4882a593Smuzhiyun module_spi_driver(acx565akm_driver);
698*4882a593Smuzhiyun 
699*4882a593Smuzhiyun MODULE_AUTHOR("Nokia Corporation");
700*4882a593Smuzhiyun MODULE_DESCRIPTION("Sony ACX565AKM LCD Panel Driver");
701*4882a593Smuzhiyun MODULE_LICENSE("GPL");
702