1caab277bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
2f76ee892STomi Valkeinen /*
3f76ee892STomi Valkeinen * Sony ACX565AKM LCD Panel driver
4f76ee892STomi Valkeinen *
5f76ee892STomi Valkeinen * Copyright (C) 2010 Nokia Corporation
6f76ee892STomi Valkeinen *
7f76ee892STomi Valkeinen * Original Driver Author: Imre Deak <imre.deak@nokia.com>
8f76ee892STomi Valkeinen * Based on panel-generic.c by Tomi Valkeinen <tomi.valkeinen@nokia.com>
9f76ee892STomi Valkeinen * Adapted to new DSS2 framework: Roger Quadros <roger.quadros@nokia.com>
10f76ee892STomi Valkeinen */
11f76ee892STomi Valkeinen
12f76ee892STomi Valkeinen #include <linux/kernel.h>
13f76ee892STomi Valkeinen #include <linux/module.h>
14f76ee892STomi Valkeinen #include <linux/platform_device.h>
15f76ee892STomi Valkeinen #include <linux/delay.h>
16f76ee892STomi Valkeinen #include <linux/spi/spi.h>
17f76ee892STomi Valkeinen #include <linux/jiffies.h>
18f76ee892STomi Valkeinen #include <linux/sched.h>
19f76ee892STomi Valkeinen #include <linux/backlight.h>
20f76ee892STomi Valkeinen #include <linux/fb.h>
21*844c245fSDmitry Torokhov #include <linux/gpio/consumer.h>
22f76ee892STomi Valkeinen #include <linux/of.h>
23f76ee892STomi Valkeinen
2462d9e44eSPeter Ujfalusi #include <video/omapfb_dss.h>
25f76ee892STomi Valkeinen
26f76ee892STomi Valkeinen #define MIPID_CMD_READ_DISP_ID 0x04
27f76ee892STomi Valkeinen #define MIPID_CMD_READ_RED 0x06
28f76ee892STomi Valkeinen #define MIPID_CMD_READ_GREEN 0x07
29f76ee892STomi Valkeinen #define MIPID_CMD_READ_BLUE 0x08
30f76ee892STomi Valkeinen #define MIPID_CMD_READ_DISP_STATUS 0x09
31f76ee892STomi Valkeinen #define MIPID_CMD_RDDSDR 0x0F
32f76ee892STomi Valkeinen #define MIPID_CMD_SLEEP_IN 0x10
33f76ee892STomi Valkeinen #define MIPID_CMD_SLEEP_OUT 0x11
34f76ee892STomi Valkeinen #define MIPID_CMD_DISP_OFF 0x28
35f76ee892STomi Valkeinen #define MIPID_CMD_DISP_ON 0x29
36f76ee892STomi Valkeinen #define MIPID_CMD_WRITE_DISP_BRIGHTNESS 0x51
37f76ee892STomi Valkeinen #define MIPID_CMD_READ_DISP_BRIGHTNESS 0x52
38f76ee892STomi Valkeinen #define MIPID_CMD_WRITE_CTRL_DISP 0x53
39f76ee892STomi Valkeinen
40f76ee892STomi Valkeinen #define CTRL_DISP_BRIGHTNESS_CTRL_ON (1 << 5)
41f76ee892STomi Valkeinen #define CTRL_DISP_AMBIENT_LIGHT_CTRL_ON (1 << 4)
42f76ee892STomi Valkeinen #define CTRL_DISP_BACKLIGHT_ON (1 << 2)
43f76ee892STomi Valkeinen #define CTRL_DISP_AUTO_BRIGHTNESS_ON (1 << 1)
44f76ee892STomi Valkeinen
45f76ee892STomi Valkeinen #define MIPID_CMD_READ_CTRL_DISP 0x54
46f76ee892STomi Valkeinen #define MIPID_CMD_WRITE_CABC 0x55
47f76ee892STomi Valkeinen #define MIPID_CMD_READ_CABC 0x56
48f76ee892STomi Valkeinen
49f76ee892STomi Valkeinen #define MIPID_VER_LPH8923 3
50f76ee892STomi Valkeinen #define MIPID_VER_LS041Y3 4
51f76ee892STomi Valkeinen #define MIPID_VER_L4F00311 8
52f76ee892STomi Valkeinen #define MIPID_VER_ACX565AKM 9
53f76ee892STomi Valkeinen
54f76ee892STomi Valkeinen struct panel_drv_data {
55f76ee892STomi Valkeinen struct omap_dss_device dssdev;
56f76ee892STomi Valkeinen struct omap_dss_device *in;
57f76ee892STomi Valkeinen
58*844c245fSDmitry Torokhov struct gpio_desc *reset_gpio;
59*844c245fSDmitry Torokhov
60f76ee892STomi Valkeinen int datapairs;
61f76ee892STomi Valkeinen
62f76ee892STomi Valkeinen struct omap_video_timings videomode;
63f76ee892STomi Valkeinen
64f76ee892STomi Valkeinen char *name;
65f76ee892STomi Valkeinen int enabled;
66f76ee892STomi Valkeinen int model;
67f76ee892STomi Valkeinen int revision;
68f76ee892STomi Valkeinen u8 display_id[3];
69f76ee892STomi Valkeinen unsigned has_bc:1;
70f76ee892STomi Valkeinen unsigned has_cabc:1;
71f76ee892STomi Valkeinen unsigned cabc_mode;
72f76ee892STomi Valkeinen unsigned long hw_guard_end; /* next value of jiffies
73f76ee892STomi Valkeinen when we can issue the
74f76ee892STomi Valkeinen next sleep in/out command */
75f76ee892STomi Valkeinen unsigned long hw_guard_wait; /* max guard time in jiffies */
76f76ee892STomi Valkeinen
77f76ee892STomi Valkeinen struct spi_device *spi;
78f76ee892STomi Valkeinen struct mutex mutex;
79f76ee892STomi Valkeinen
80f76ee892STomi Valkeinen struct backlight_device *bl_dev;
81f76ee892STomi Valkeinen };
82f76ee892STomi Valkeinen
83f76ee892STomi Valkeinen static const struct omap_video_timings acx565akm_panel_timings = {
84f76ee892STomi Valkeinen .x_res = 800,
85f76ee892STomi Valkeinen .y_res = 480,
86f76ee892STomi Valkeinen .pixelclock = 24000000,
87f76ee892STomi Valkeinen .hfp = 28,
88f76ee892STomi Valkeinen .hsw = 4,
89f76ee892STomi Valkeinen .hbp = 24,
90f76ee892STomi Valkeinen .vfp = 3,
91f76ee892STomi Valkeinen .vsw = 3,
92f76ee892STomi Valkeinen .vbp = 4,
93f76ee892STomi Valkeinen
94f76ee892STomi Valkeinen .vsync_level = OMAPDSS_SIG_ACTIVE_LOW,
95f76ee892STomi Valkeinen .hsync_level = OMAPDSS_SIG_ACTIVE_LOW,
96f76ee892STomi Valkeinen
97f76ee892STomi Valkeinen .data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE,
98f76ee892STomi Valkeinen .de_level = OMAPDSS_SIG_ACTIVE_HIGH,
99f76ee892STomi Valkeinen .sync_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE,
100f76ee892STomi Valkeinen };
101f76ee892STomi Valkeinen
102f76ee892STomi Valkeinen #define to_panel_data(p) container_of(p, struct panel_drv_data, dssdev)
103f76ee892STomi Valkeinen
acx565akm_transfer(struct panel_drv_data * ddata,int cmd,const u8 * wbuf,int wlen,u8 * rbuf,int rlen)104f76ee892STomi Valkeinen static void acx565akm_transfer(struct panel_drv_data *ddata, int cmd,
105f76ee892STomi Valkeinen const u8 *wbuf, int wlen, u8 *rbuf, int rlen)
106f76ee892STomi Valkeinen {
107f76ee892STomi Valkeinen struct spi_message m;
108f76ee892STomi Valkeinen struct spi_transfer *x, xfer[5];
109f76ee892STomi Valkeinen int r;
110f76ee892STomi Valkeinen
111f76ee892STomi Valkeinen BUG_ON(ddata->spi == NULL);
112f76ee892STomi Valkeinen
113f76ee892STomi Valkeinen spi_message_init(&m);
114f76ee892STomi Valkeinen
115f76ee892STomi Valkeinen memset(xfer, 0, sizeof(xfer));
116f76ee892STomi Valkeinen x = &xfer[0];
117f76ee892STomi Valkeinen
118f76ee892STomi Valkeinen cmd &= 0xff;
119f76ee892STomi Valkeinen x->tx_buf = &cmd;
120f76ee892STomi Valkeinen x->bits_per_word = 9;
121f76ee892STomi Valkeinen x->len = 2;
122f76ee892STomi Valkeinen
123f76ee892STomi Valkeinen if (rlen > 1 && wlen == 0) {
124f76ee892STomi Valkeinen /*
125f76ee892STomi Valkeinen * Between the command and the response data there is a
126f76ee892STomi Valkeinen * dummy clock cycle. Add an extra bit after the command
127f76ee892STomi Valkeinen * word to account for this.
128f76ee892STomi Valkeinen */
129f76ee892STomi Valkeinen x->bits_per_word = 10;
130f76ee892STomi Valkeinen cmd <<= 1;
131f76ee892STomi Valkeinen }
132f76ee892STomi Valkeinen spi_message_add_tail(x, &m);
133f76ee892STomi Valkeinen
134f76ee892STomi Valkeinen if (wlen) {
135f76ee892STomi Valkeinen x++;
136f76ee892STomi Valkeinen x->tx_buf = wbuf;
137f76ee892STomi Valkeinen x->len = wlen;
138f76ee892STomi Valkeinen x->bits_per_word = 9;
139f76ee892STomi Valkeinen spi_message_add_tail(x, &m);
140f76ee892STomi Valkeinen }
141f76ee892STomi Valkeinen
142f76ee892STomi Valkeinen if (rlen) {
143f76ee892STomi Valkeinen x++;
144f76ee892STomi Valkeinen x->rx_buf = rbuf;
145f76ee892STomi Valkeinen x->len = rlen;
146f76ee892STomi Valkeinen spi_message_add_tail(x, &m);
147f76ee892STomi Valkeinen }
148f76ee892STomi Valkeinen
149f76ee892STomi Valkeinen r = spi_sync(ddata->spi, &m);
150f76ee892STomi Valkeinen if (r < 0)
151f76ee892STomi Valkeinen dev_dbg(&ddata->spi->dev, "spi_sync %d\n", r);
152f76ee892STomi Valkeinen }
153f76ee892STomi Valkeinen
acx565akm_cmd(struct panel_drv_data * ddata,int cmd)154f76ee892STomi Valkeinen static inline void acx565akm_cmd(struct panel_drv_data *ddata, int cmd)
155f76ee892STomi Valkeinen {
156f76ee892STomi Valkeinen acx565akm_transfer(ddata, cmd, NULL, 0, NULL, 0);
157f76ee892STomi Valkeinen }
158f76ee892STomi Valkeinen
acx565akm_write(struct panel_drv_data * ddata,int reg,const u8 * buf,int len)159f76ee892STomi Valkeinen static inline void acx565akm_write(struct panel_drv_data *ddata,
160f76ee892STomi Valkeinen int reg, const u8 *buf, int len)
161f76ee892STomi Valkeinen {
162f76ee892STomi Valkeinen acx565akm_transfer(ddata, reg, buf, len, NULL, 0);
163f76ee892STomi Valkeinen }
164f76ee892STomi Valkeinen
acx565akm_read(struct panel_drv_data * ddata,int reg,u8 * buf,int len)165f76ee892STomi Valkeinen static inline void acx565akm_read(struct panel_drv_data *ddata,
166f76ee892STomi Valkeinen int reg, u8 *buf, int len)
167f76ee892STomi Valkeinen {
168f76ee892STomi Valkeinen acx565akm_transfer(ddata, reg, NULL, 0, buf, len);
169f76ee892STomi Valkeinen }
170f76ee892STomi Valkeinen
hw_guard_start(struct panel_drv_data * ddata,int guard_msec)171f76ee892STomi Valkeinen static void hw_guard_start(struct panel_drv_data *ddata, int guard_msec)
172f76ee892STomi Valkeinen {
173f76ee892STomi Valkeinen ddata->hw_guard_wait = msecs_to_jiffies(guard_msec);
174f76ee892STomi Valkeinen ddata->hw_guard_end = jiffies + ddata->hw_guard_wait;
175f76ee892STomi Valkeinen }
176f76ee892STomi Valkeinen
hw_guard_wait(struct panel_drv_data * ddata)177f76ee892STomi Valkeinen static void hw_guard_wait(struct panel_drv_data *ddata)
178f76ee892STomi Valkeinen {
179f76ee892STomi Valkeinen unsigned long wait = ddata->hw_guard_end - jiffies;
180f76ee892STomi Valkeinen
181f76ee892STomi Valkeinen if ((long)wait > 0 && wait <= ddata->hw_guard_wait) {
182f76ee892STomi Valkeinen set_current_state(TASK_UNINTERRUPTIBLE);
183f76ee892STomi Valkeinen schedule_timeout(wait);
184f76ee892STomi Valkeinen }
185f76ee892STomi Valkeinen }
186f76ee892STomi Valkeinen
set_sleep_mode(struct panel_drv_data * ddata,int on)187f76ee892STomi Valkeinen static void set_sleep_mode(struct panel_drv_data *ddata, int on)
188f76ee892STomi Valkeinen {
189f76ee892STomi Valkeinen int cmd;
190f76ee892STomi Valkeinen
191f76ee892STomi Valkeinen if (on)
192f76ee892STomi Valkeinen cmd = MIPID_CMD_SLEEP_IN;
193f76ee892STomi Valkeinen else
194f76ee892STomi Valkeinen cmd = MIPID_CMD_SLEEP_OUT;
195f76ee892STomi Valkeinen /*
196f76ee892STomi Valkeinen * We have to keep 120msec between sleep in/out commands.
197f76ee892STomi Valkeinen * (8.2.15, 8.2.16).
198f76ee892STomi Valkeinen */
199f76ee892STomi Valkeinen hw_guard_wait(ddata);
200f76ee892STomi Valkeinen acx565akm_cmd(ddata, cmd);
201f76ee892STomi Valkeinen hw_guard_start(ddata, 120);
202f76ee892STomi Valkeinen }
203f76ee892STomi Valkeinen
set_display_state(struct panel_drv_data * ddata,int enabled)204f76ee892STomi Valkeinen static void set_display_state(struct panel_drv_data *ddata, int enabled)
205f76ee892STomi Valkeinen {
206f76ee892STomi Valkeinen int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF;
207f76ee892STomi Valkeinen
208f76ee892STomi Valkeinen acx565akm_cmd(ddata, cmd);
209f76ee892STomi Valkeinen }
210f76ee892STomi Valkeinen
panel_enabled(struct panel_drv_data * ddata)211f76ee892STomi Valkeinen static int panel_enabled(struct panel_drv_data *ddata)
212f76ee892STomi Valkeinen {
213f76ee892STomi Valkeinen u32 disp_status;
214f76ee892STomi Valkeinen int enabled;
215f76ee892STomi Valkeinen
216f76ee892STomi Valkeinen acx565akm_read(ddata, MIPID_CMD_READ_DISP_STATUS,
217f76ee892STomi Valkeinen (u8 *)&disp_status, 4);
218f76ee892STomi Valkeinen disp_status = __be32_to_cpu(disp_status);
219f76ee892STomi Valkeinen enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10));
220f76ee892STomi Valkeinen dev_dbg(&ddata->spi->dev,
221f76ee892STomi Valkeinen "LCD panel %senabled by bootloader (status 0x%04x)\n",
222f76ee892STomi Valkeinen enabled ? "" : "not ", disp_status);
223f76ee892STomi Valkeinen return enabled;
224f76ee892STomi Valkeinen }
225f76ee892STomi Valkeinen
panel_detect(struct panel_drv_data * ddata)226f76ee892STomi Valkeinen static int panel_detect(struct panel_drv_data *ddata)
227f76ee892STomi Valkeinen {
228f76ee892STomi Valkeinen acx565akm_read(ddata, MIPID_CMD_READ_DISP_ID, ddata->display_id, 3);
229f76ee892STomi Valkeinen dev_dbg(&ddata->spi->dev, "MIPI display ID: %02x%02x%02x\n",
230f76ee892STomi Valkeinen ddata->display_id[0],
231f76ee892STomi Valkeinen ddata->display_id[1],
232f76ee892STomi Valkeinen ddata->display_id[2]);
233f76ee892STomi Valkeinen
234f76ee892STomi Valkeinen switch (ddata->display_id[0]) {
235f76ee892STomi Valkeinen case 0x10:
236f76ee892STomi Valkeinen ddata->model = MIPID_VER_ACX565AKM;
237f76ee892STomi Valkeinen ddata->name = "acx565akm";
238f76ee892STomi Valkeinen ddata->has_bc = 1;
239f76ee892STomi Valkeinen ddata->has_cabc = 1;
240f76ee892STomi Valkeinen break;
241f76ee892STomi Valkeinen case 0x29:
242f76ee892STomi Valkeinen ddata->model = MIPID_VER_L4F00311;
243f76ee892STomi Valkeinen ddata->name = "l4f00311";
244f76ee892STomi Valkeinen break;
245f76ee892STomi Valkeinen case 0x45:
246f76ee892STomi Valkeinen ddata->model = MIPID_VER_LPH8923;
247f76ee892STomi Valkeinen ddata->name = "lph8923";
248f76ee892STomi Valkeinen break;
249f76ee892STomi Valkeinen case 0x83:
250f76ee892STomi Valkeinen ddata->model = MIPID_VER_LS041Y3;
251f76ee892STomi Valkeinen ddata->name = "ls041y3";
252f76ee892STomi Valkeinen break;
253f76ee892STomi Valkeinen default:
254f76ee892STomi Valkeinen ddata->name = "unknown";
255f76ee892STomi Valkeinen dev_err(&ddata->spi->dev, "invalid display ID\n");
256f76ee892STomi Valkeinen return -ENODEV;
257f76ee892STomi Valkeinen }
258f76ee892STomi Valkeinen
259f76ee892STomi Valkeinen ddata->revision = ddata->display_id[1];
260f76ee892STomi Valkeinen
261f76ee892STomi Valkeinen dev_info(&ddata->spi->dev, "omapfb: %s rev %02x LCD detected\n",
262f76ee892STomi Valkeinen ddata->name, ddata->revision);
263f76ee892STomi Valkeinen
264f76ee892STomi Valkeinen return 0;
265f76ee892STomi Valkeinen }
266f76ee892STomi Valkeinen
267f76ee892STomi Valkeinen /*----------------------Backlight Control-------------------------*/
268f76ee892STomi Valkeinen
enable_backlight_ctrl(struct panel_drv_data * ddata,int enable)269f76ee892STomi Valkeinen static void enable_backlight_ctrl(struct panel_drv_data *ddata, int enable)
270f76ee892STomi Valkeinen {
271f76ee892STomi Valkeinen u16 ctrl;
272f76ee892STomi Valkeinen
273f76ee892STomi Valkeinen acx565akm_read(ddata, MIPID_CMD_READ_CTRL_DISP, (u8 *)&ctrl, 1);
274f76ee892STomi Valkeinen if (enable) {
275f76ee892STomi Valkeinen ctrl |= CTRL_DISP_BRIGHTNESS_CTRL_ON |
276f76ee892STomi Valkeinen CTRL_DISP_BACKLIGHT_ON;
277f76ee892STomi Valkeinen } else {
278f76ee892STomi Valkeinen ctrl &= ~(CTRL_DISP_BRIGHTNESS_CTRL_ON |
279f76ee892STomi Valkeinen CTRL_DISP_BACKLIGHT_ON);
280f76ee892STomi Valkeinen }
281f76ee892STomi Valkeinen
282f76ee892STomi Valkeinen ctrl |= 1 << 8;
283f76ee892STomi Valkeinen acx565akm_write(ddata, MIPID_CMD_WRITE_CTRL_DISP, (u8 *)&ctrl, 2);
284f76ee892STomi Valkeinen }
285f76ee892STomi Valkeinen
set_cabc_mode(struct panel_drv_data * ddata,unsigned mode)286f76ee892STomi Valkeinen static void set_cabc_mode(struct panel_drv_data *ddata, unsigned mode)
287f76ee892STomi Valkeinen {
288f76ee892STomi Valkeinen u16 cabc_ctrl;
289f76ee892STomi Valkeinen
290f76ee892STomi Valkeinen ddata->cabc_mode = mode;
291f76ee892STomi Valkeinen if (!ddata->enabled)
292f76ee892STomi Valkeinen return;
293f76ee892STomi Valkeinen cabc_ctrl = 0;
294f76ee892STomi Valkeinen acx565akm_read(ddata, MIPID_CMD_READ_CABC, (u8 *)&cabc_ctrl, 1);
295f76ee892STomi Valkeinen cabc_ctrl &= ~3;
296f76ee892STomi Valkeinen cabc_ctrl |= (1 << 8) | (mode & 3);
297f76ee892STomi Valkeinen acx565akm_write(ddata, MIPID_CMD_WRITE_CABC, (u8 *)&cabc_ctrl, 2);
298f76ee892STomi Valkeinen }
299f76ee892STomi Valkeinen
get_cabc_mode(struct panel_drv_data * ddata)300f76ee892STomi Valkeinen static unsigned get_cabc_mode(struct panel_drv_data *ddata)
301f76ee892STomi Valkeinen {
302f76ee892STomi Valkeinen return ddata->cabc_mode;
303f76ee892STomi Valkeinen }
304f76ee892STomi Valkeinen
get_hw_cabc_mode(struct panel_drv_data * ddata)305f76ee892STomi Valkeinen static unsigned get_hw_cabc_mode(struct panel_drv_data *ddata)
306f76ee892STomi Valkeinen {
307f76ee892STomi Valkeinen u8 cabc_ctrl;
308f76ee892STomi Valkeinen
309f76ee892STomi Valkeinen acx565akm_read(ddata, MIPID_CMD_READ_CABC, &cabc_ctrl, 1);
310f76ee892STomi Valkeinen return cabc_ctrl & 3;
311f76ee892STomi Valkeinen }
312f76ee892STomi Valkeinen
acx565akm_set_brightness(struct panel_drv_data * ddata,int level)313f76ee892STomi Valkeinen static void acx565akm_set_brightness(struct panel_drv_data *ddata, int level)
314f76ee892STomi Valkeinen {
315f76ee892STomi Valkeinen int bv;
316f76ee892STomi Valkeinen
317f76ee892STomi Valkeinen bv = level | (1 << 8);
318f76ee892STomi Valkeinen acx565akm_write(ddata, MIPID_CMD_WRITE_DISP_BRIGHTNESS, (u8 *)&bv, 2);
319f76ee892STomi Valkeinen
320f76ee892STomi Valkeinen if (level)
321f76ee892STomi Valkeinen enable_backlight_ctrl(ddata, 1);
322f76ee892STomi Valkeinen else
323f76ee892STomi Valkeinen enable_backlight_ctrl(ddata, 0);
324f76ee892STomi Valkeinen }
325f76ee892STomi Valkeinen
acx565akm_get_actual_brightness(struct panel_drv_data * ddata)326f76ee892STomi Valkeinen static int acx565akm_get_actual_brightness(struct panel_drv_data *ddata)
327f76ee892STomi Valkeinen {
328f76ee892STomi Valkeinen u8 bv;
329f76ee892STomi Valkeinen
330f76ee892STomi Valkeinen acx565akm_read(ddata, MIPID_CMD_READ_DISP_BRIGHTNESS, &bv, 1);
331f76ee892STomi Valkeinen
332f76ee892STomi Valkeinen return bv;
333f76ee892STomi Valkeinen }
334f76ee892STomi Valkeinen
335f76ee892STomi Valkeinen
acx565akm_bl_update_status(struct backlight_device * dev)336f76ee892STomi Valkeinen static int acx565akm_bl_update_status(struct backlight_device *dev)
337f76ee892STomi Valkeinen {
338f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
339f76ee892STomi Valkeinen int level;
340f76ee892STomi Valkeinen
341f76ee892STomi Valkeinen dev_dbg(&ddata->spi->dev, "%s\n", __func__);
342f76ee892STomi Valkeinen
343f76ee892STomi Valkeinen if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
344f76ee892STomi Valkeinen dev->props.power == FB_BLANK_UNBLANK)
345f76ee892STomi Valkeinen level = dev->props.brightness;
346f76ee892STomi Valkeinen else
347f76ee892STomi Valkeinen level = 0;
348f76ee892STomi Valkeinen
349f76ee892STomi Valkeinen if (ddata->has_bc)
350f76ee892STomi Valkeinen acx565akm_set_brightness(ddata, level);
351f76ee892STomi Valkeinen else
352f76ee892STomi Valkeinen return -ENODEV;
353f76ee892STomi Valkeinen
354f76ee892STomi Valkeinen return 0;
355f76ee892STomi Valkeinen }
356f76ee892STomi Valkeinen
acx565akm_bl_get_intensity(struct backlight_device * dev)357f76ee892STomi Valkeinen static int acx565akm_bl_get_intensity(struct backlight_device *dev)
358f76ee892STomi Valkeinen {
359f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
360f76ee892STomi Valkeinen
361f76ee892STomi Valkeinen dev_dbg(&dev->dev, "%s\n", __func__);
362f76ee892STomi Valkeinen
363f76ee892STomi Valkeinen if (!ddata->has_bc)
364f76ee892STomi Valkeinen return -ENODEV;
365f76ee892STomi Valkeinen
366f76ee892STomi Valkeinen if (dev->props.fb_blank == FB_BLANK_UNBLANK &&
367f76ee892STomi Valkeinen dev->props.power == FB_BLANK_UNBLANK) {
368f76ee892STomi Valkeinen if (ddata->has_bc)
369f76ee892STomi Valkeinen return acx565akm_get_actual_brightness(ddata);
370f76ee892STomi Valkeinen else
371f76ee892STomi Valkeinen return dev->props.brightness;
372f76ee892STomi Valkeinen }
373f76ee892STomi Valkeinen
374f76ee892STomi Valkeinen return 0;
375f76ee892STomi Valkeinen }
376f76ee892STomi Valkeinen
acx565akm_bl_update_status_locked(struct backlight_device * dev)377f76ee892STomi Valkeinen static int acx565akm_bl_update_status_locked(struct backlight_device *dev)
378f76ee892STomi Valkeinen {
379f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
380f76ee892STomi Valkeinen int r;
381f76ee892STomi Valkeinen
382f76ee892STomi Valkeinen mutex_lock(&ddata->mutex);
383f76ee892STomi Valkeinen r = acx565akm_bl_update_status(dev);
384f76ee892STomi Valkeinen mutex_unlock(&ddata->mutex);
385f76ee892STomi Valkeinen
386f76ee892STomi Valkeinen return r;
387f76ee892STomi Valkeinen }
388f76ee892STomi Valkeinen
acx565akm_bl_get_intensity_locked(struct backlight_device * dev)389f76ee892STomi Valkeinen static int acx565akm_bl_get_intensity_locked(struct backlight_device *dev)
390f76ee892STomi Valkeinen {
391f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(&dev->dev);
392f76ee892STomi Valkeinen int r;
393f76ee892STomi Valkeinen
394f76ee892STomi Valkeinen mutex_lock(&ddata->mutex);
395f76ee892STomi Valkeinen r = acx565akm_bl_get_intensity(dev);
396f76ee892STomi Valkeinen mutex_unlock(&ddata->mutex);
397f76ee892STomi Valkeinen
398f76ee892STomi Valkeinen return r;
399f76ee892STomi Valkeinen }
400f76ee892STomi Valkeinen
401f76ee892STomi Valkeinen static const struct backlight_ops acx565akm_bl_ops = {
402f76ee892STomi Valkeinen .get_brightness = acx565akm_bl_get_intensity_locked,
403f76ee892STomi Valkeinen .update_status = acx565akm_bl_update_status_locked,
404f76ee892STomi Valkeinen };
405f76ee892STomi Valkeinen
406f76ee892STomi Valkeinen /*--------------------Auto Brightness control via Sysfs---------------------*/
407f76ee892STomi Valkeinen
408f76ee892STomi Valkeinen static const char * const cabc_modes[] = {
409f76ee892STomi Valkeinen "off", /* always used when CABC is not supported */
410f76ee892STomi Valkeinen "ui",
411f76ee892STomi Valkeinen "still-image",
412f76ee892STomi Valkeinen "moving-image",
413f76ee892STomi Valkeinen };
414f76ee892STomi Valkeinen
show_cabc_mode(struct device * dev,struct device_attribute * attr,char * buf)415f76ee892STomi Valkeinen static ssize_t show_cabc_mode(struct device *dev,
416f76ee892STomi Valkeinen struct device_attribute *attr,
417f76ee892STomi Valkeinen char *buf)
418f76ee892STomi Valkeinen {
419f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(dev);
420f76ee892STomi Valkeinen const char *mode_str;
421f76ee892STomi Valkeinen int mode;
422f76ee892STomi Valkeinen int len;
423f76ee892STomi Valkeinen
424f76ee892STomi Valkeinen if (!ddata->has_cabc)
425f76ee892STomi Valkeinen mode = 0;
426f76ee892STomi Valkeinen else
427f76ee892STomi Valkeinen mode = get_cabc_mode(ddata);
428f76ee892STomi Valkeinen mode_str = "unknown";
429f76ee892STomi Valkeinen if (mode >= 0 && mode < ARRAY_SIZE(cabc_modes))
430f76ee892STomi Valkeinen mode_str = cabc_modes[mode];
431f76ee892STomi Valkeinen len = snprintf(buf, PAGE_SIZE, "%s\n", mode_str);
432f76ee892STomi Valkeinen
433f76ee892STomi Valkeinen return len < PAGE_SIZE - 1 ? len : PAGE_SIZE - 1;
434f76ee892STomi Valkeinen }
435f76ee892STomi Valkeinen
store_cabc_mode(struct device * dev,struct device_attribute * attr,const char * buf,size_t count)436f76ee892STomi Valkeinen static ssize_t store_cabc_mode(struct device *dev,
437f76ee892STomi Valkeinen struct device_attribute *attr,
438f76ee892STomi Valkeinen const char *buf, size_t count)
439f76ee892STomi Valkeinen {
440f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(dev);
441f76ee892STomi Valkeinen int i;
442f76ee892STomi Valkeinen
443f76ee892STomi Valkeinen for (i = 0; i < ARRAY_SIZE(cabc_modes); i++) {
444f76ee892STomi Valkeinen const char *mode_str = cabc_modes[i];
445f76ee892STomi Valkeinen int cmp_len = strlen(mode_str);
446f76ee892STomi Valkeinen
447f76ee892STomi Valkeinen if (count > 0 && buf[count - 1] == '\n')
448f76ee892STomi Valkeinen count--;
449f76ee892STomi Valkeinen if (count != cmp_len)
450f76ee892STomi Valkeinen continue;
451f76ee892STomi Valkeinen
452f76ee892STomi Valkeinen if (strncmp(buf, mode_str, cmp_len) == 0)
453f76ee892STomi Valkeinen break;
454f76ee892STomi Valkeinen }
455f76ee892STomi Valkeinen
456f76ee892STomi Valkeinen if (i == ARRAY_SIZE(cabc_modes))
457f76ee892STomi Valkeinen return -EINVAL;
458f76ee892STomi Valkeinen
459f76ee892STomi Valkeinen if (!ddata->has_cabc && i != 0)
460f76ee892STomi Valkeinen return -EINVAL;
461f76ee892STomi Valkeinen
462f76ee892STomi Valkeinen mutex_lock(&ddata->mutex);
463f76ee892STomi Valkeinen set_cabc_mode(ddata, i);
464f76ee892STomi Valkeinen mutex_unlock(&ddata->mutex);
465f76ee892STomi Valkeinen
466f76ee892STomi Valkeinen return count;
467f76ee892STomi Valkeinen }
468f76ee892STomi Valkeinen
show_cabc_available_modes(struct device * dev,struct device_attribute * attr,char * buf)469f76ee892STomi Valkeinen static ssize_t show_cabc_available_modes(struct device *dev,
470f76ee892STomi Valkeinen struct device_attribute *attr,
471f76ee892STomi Valkeinen char *buf)
472f76ee892STomi Valkeinen {
473f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(dev);
474f76ee892STomi Valkeinen int len;
475f76ee892STomi Valkeinen int i;
476f76ee892STomi Valkeinen
477f76ee892STomi Valkeinen if (!ddata->has_cabc)
47824565bc4SYang Guang return sysfs_emit(buf, "%s\n", cabc_modes[0]);
479f76ee892STomi Valkeinen
480f76ee892STomi Valkeinen for (i = 0, len = 0;
481f76ee892STomi Valkeinen len < PAGE_SIZE && i < ARRAY_SIZE(cabc_modes); i++)
482f76ee892STomi Valkeinen len += snprintf(&buf[len], PAGE_SIZE - len, "%s%s%s",
483f76ee892STomi Valkeinen i ? " " : "", cabc_modes[i],
484f76ee892STomi Valkeinen i == ARRAY_SIZE(cabc_modes) - 1 ? "\n" : "");
485f76ee892STomi Valkeinen
486f76ee892STomi Valkeinen return len < PAGE_SIZE ? len : PAGE_SIZE - 1;
487f76ee892STomi Valkeinen }
488f76ee892STomi Valkeinen
489f76ee892STomi Valkeinen static DEVICE_ATTR(cabc_mode, S_IRUGO | S_IWUSR,
490f76ee892STomi Valkeinen show_cabc_mode, store_cabc_mode);
491f76ee892STomi Valkeinen static DEVICE_ATTR(cabc_available_modes, S_IRUGO,
492f76ee892STomi Valkeinen show_cabc_available_modes, NULL);
493f76ee892STomi Valkeinen
494f76ee892STomi Valkeinen static struct attribute *bldev_attrs[] = {
495f76ee892STomi Valkeinen &dev_attr_cabc_mode.attr,
496f76ee892STomi Valkeinen &dev_attr_cabc_available_modes.attr,
497f76ee892STomi Valkeinen NULL,
498f76ee892STomi Valkeinen };
499f76ee892STomi Valkeinen
500a8fb2d65SArvind Yadav static const struct attribute_group bldev_attr_group = {
501f76ee892STomi Valkeinen .attrs = bldev_attrs,
502f76ee892STomi Valkeinen };
503f76ee892STomi Valkeinen
acx565akm_connect(struct omap_dss_device * dssdev)504f76ee892STomi Valkeinen static int acx565akm_connect(struct omap_dss_device *dssdev)
505f76ee892STomi Valkeinen {
506f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
507f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in;
508f76ee892STomi Valkeinen
509f76ee892STomi Valkeinen if (omapdss_device_is_connected(dssdev))
510f76ee892STomi Valkeinen return 0;
511f76ee892STomi Valkeinen
51210732a0fSQinglang Miao return in->ops.sdi->connect(in, dssdev);
513f76ee892STomi Valkeinen }
514f76ee892STomi Valkeinen
acx565akm_disconnect(struct omap_dss_device * dssdev)515f76ee892STomi Valkeinen static void acx565akm_disconnect(struct omap_dss_device *dssdev)
516f76ee892STomi Valkeinen {
517f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
518f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in;
519f76ee892STomi Valkeinen
520f76ee892STomi Valkeinen if (!omapdss_device_is_connected(dssdev))
521f76ee892STomi Valkeinen return;
522f76ee892STomi Valkeinen
523f76ee892STomi Valkeinen in->ops.sdi->disconnect(in, dssdev);
524f76ee892STomi Valkeinen }
525f76ee892STomi Valkeinen
acx565akm_panel_power_on(struct omap_dss_device * dssdev)526f76ee892STomi Valkeinen static int acx565akm_panel_power_on(struct omap_dss_device *dssdev)
527f76ee892STomi Valkeinen {
528f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
529f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in;
530f76ee892STomi Valkeinen int r;
531f76ee892STomi Valkeinen
532f76ee892STomi Valkeinen dev_dbg(&ddata->spi->dev, "%s\n", __func__);
533f76ee892STomi Valkeinen
534f76ee892STomi Valkeinen in->ops.sdi->set_timings(in, &ddata->videomode);
535f76ee892STomi Valkeinen
536f76ee892STomi Valkeinen if (ddata->datapairs > 0)
537f76ee892STomi Valkeinen in->ops.sdi->set_datapairs(in, ddata->datapairs);
538f76ee892STomi Valkeinen
539f76ee892STomi Valkeinen r = in->ops.sdi->enable(in);
540f76ee892STomi Valkeinen if (r) {
541f76ee892STomi Valkeinen pr_err("%s sdi enable failed\n", __func__);
542f76ee892STomi Valkeinen return r;
543f76ee892STomi Valkeinen }
544f76ee892STomi Valkeinen
545f76ee892STomi Valkeinen /*FIXME tweak me */
546f76ee892STomi Valkeinen msleep(50);
547f76ee892STomi Valkeinen
548*844c245fSDmitry Torokhov /*
549*844c245fSDmitry Torokhov * Note that we appear to activate the reset line here. However
550*844c245fSDmitry Torokhov * existing DTSes specified incorrect polarity for it (active high),
551*844c245fSDmitry Torokhov * so in fact this deasserts the reset line.
552*844c245fSDmitry Torokhov */
553*844c245fSDmitry Torokhov if (ddata->reset_gpio)
554*844c245fSDmitry Torokhov gpiod_set_value_cansleep(ddata->reset_gpio, 1);
555f76ee892STomi Valkeinen
556f76ee892STomi Valkeinen if (ddata->enabled) {
557f76ee892STomi Valkeinen dev_dbg(&ddata->spi->dev, "panel already enabled\n");
558f76ee892STomi Valkeinen return 0;
559f76ee892STomi Valkeinen }
560f76ee892STomi Valkeinen
561f76ee892STomi Valkeinen /*
562f76ee892STomi Valkeinen * We have to meet all the following delay requirements:
563f76ee892STomi Valkeinen * 1. tRW: reset pulse width 10usec (7.12.1)
564f76ee892STomi Valkeinen * 2. tRT: reset cancel time 5msec (7.12.1)
565f76ee892STomi Valkeinen * 3. Providing PCLK,HS,VS signals for 2 frames = ~50msec worst
566f76ee892STomi Valkeinen * case (7.6.2)
567f76ee892STomi Valkeinen * 4. 120msec before the sleep out command (7.12.1)
568f76ee892STomi Valkeinen */
569f76ee892STomi Valkeinen msleep(120);
570f76ee892STomi Valkeinen
571f76ee892STomi Valkeinen set_sleep_mode(ddata, 0);
572f76ee892STomi Valkeinen ddata->enabled = 1;
573f76ee892STomi Valkeinen
574f76ee892STomi Valkeinen /* 5msec between sleep out and the next command. (8.2.16) */
575f76ee892STomi Valkeinen usleep_range(5000, 10000);
576f76ee892STomi Valkeinen set_display_state(ddata, 1);
577f76ee892STomi Valkeinen set_cabc_mode(ddata, ddata->cabc_mode);
578f76ee892STomi Valkeinen
579f76ee892STomi Valkeinen return acx565akm_bl_update_status(ddata->bl_dev);
580f76ee892STomi Valkeinen }
581f76ee892STomi Valkeinen
acx565akm_panel_power_off(struct omap_dss_device * dssdev)582f76ee892STomi Valkeinen static void acx565akm_panel_power_off(struct omap_dss_device *dssdev)
583f76ee892STomi Valkeinen {
584f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
585f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in;
586f76ee892STomi Valkeinen
587f76ee892STomi Valkeinen dev_dbg(dssdev->dev, "%s\n", __func__);
588f76ee892STomi Valkeinen
589f76ee892STomi Valkeinen if (!ddata->enabled)
590f76ee892STomi Valkeinen return;
591f76ee892STomi Valkeinen
592f76ee892STomi Valkeinen set_display_state(ddata, 0);
593f76ee892STomi Valkeinen set_sleep_mode(ddata, 1);
594f76ee892STomi Valkeinen ddata->enabled = 0;
595f76ee892STomi Valkeinen /*
596f76ee892STomi Valkeinen * We have to provide PCLK,HS,VS signals for 2 frames (worst case
597f76ee892STomi Valkeinen * ~50msec) after sending the sleep in command and asserting the
598f76ee892STomi Valkeinen * reset signal. We probably could assert the reset w/o the delay
599f76ee892STomi Valkeinen * but we still delay to avoid possible artifacts. (7.6.1)
600f76ee892STomi Valkeinen */
601f76ee892STomi Valkeinen msleep(50);
602f76ee892STomi Valkeinen
603*844c245fSDmitry Torokhov /* see comment in acx565akm_panel_power_on() */
604*844c245fSDmitry Torokhov if (ddata->reset_gpio)
605*844c245fSDmitry Torokhov gpiod_set_value_cansleep(ddata->reset_gpio, 0);
606f76ee892STomi Valkeinen
607f76ee892STomi Valkeinen /* FIXME need to tweak this delay */
608f76ee892STomi Valkeinen msleep(100);
609f76ee892STomi Valkeinen
610f76ee892STomi Valkeinen in->ops.sdi->disable(in);
611f76ee892STomi Valkeinen }
612f76ee892STomi Valkeinen
acx565akm_enable(struct omap_dss_device * dssdev)613f76ee892STomi Valkeinen static int acx565akm_enable(struct omap_dss_device *dssdev)
614f76ee892STomi Valkeinen {
615f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
616f76ee892STomi Valkeinen int r;
617f76ee892STomi Valkeinen
618f76ee892STomi Valkeinen dev_dbg(dssdev->dev, "%s\n", __func__);
619f76ee892STomi Valkeinen
620f76ee892STomi Valkeinen if (!omapdss_device_is_connected(dssdev))
621f76ee892STomi Valkeinen return -ENODEV;
622f76ee892STomi Valkeinen
623f76ee892STomi Valkeinen if (omapdss_device_is_enabled(dssdev))
624f76ee892STomi Valkeinen return 0;
625f76ee892STomi Valkeinen
626f76ee892STomi Valkeinen mutex_lock(&ddata->mutex);
627f76ee892STomi Valkeinen r = acx565akm_panel_power_on(dssdev);
628f76ee892STomi Valkeinen mutex_unlock(&ddata->mutex);
629f76ee892STomi Valkeinen if (r)
630f76ee892STomi Valkeinen return r;
631f76ee892STomi Valkeinen
632f76ee892STomi Valkeinen dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
633f76ee892STomi Valkeinen
634f76ee892STomi Valkeinen return 0;
635f76ee892STomi Valkeinen }
636f76ee892STomi Valkeinen
acx565akm_disable(struct omap_dss_device * dssdev)637f76ee892STomi Valkeinen static void acx565akm_disable(struct omap_dss_device *dssdev)
638f76ee892STomi Valkeinen {
639f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
640f76ee892STomi Valkeinen
641f76ee892STomi Valkeinen dev_dbg(dssdev->dev, "%s\n", __func__);
642f76ee892STomi Valkeinen
643f76ee892STomi Valkeinen if (!omapdss_device_is_enabled(dssdev))
644f76ee892STomi Valkeinen return;
645f76ee892STomi Valkeinen
646f76ee892STomi Valkeinen mutex_lock(&ddata->mutex);
647f76ee892STomi Valkeinen acx565akm_panel_power_off(dssdev);
648f76ee892STomi Valkeinen mutex_unlock(&ddata->mutex);
649f76ee892STomi Valkeinen
650f76ee892STomi Valkeinen dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
651f76ee892STomi Valkeinen }
652f76ee892STomi Valkeinen
acx565akm_set_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)653f76ee892STomi Valkeinen static void acx565akm_set_timings(struct omap_dss_device *dssdev,
654f76ee892STomi Valkeinen struct omap_video_timings *timings)
655f76ee892STomi Valkeinen {
656f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
657f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in;
658f76ee892STomi Valkeinen
659f76ee892STomi Valkeinen ddata->videomode = *timings;
660f76ee892STomi Valkeinen dssdev->panel.timings = *timings;
661f76ee892STomi Valkeinen
662f76ee892STomi Valkeinen in->ops.sdi->set_timings(in, timings);
663f76ee892STomi Valkeinen }
664f76ee892STomi Valkeinen
acx565akm_get_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)665f76ee892STomi Valkeinen static void acx565akm_get_timings(struct omap_dss_device *dssdev,
666f76ee892STomi Valkeinen struct omap_video_timings *timings)
667f76ee892STomi Valkeinen {
668f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
669f76ee892STomi Valkeinen
670f76ee892STomi Valkeinen *timings = ddata->videomode;
671f76ee892STomi Valkeinen }
672f76ee892STomi Valkeinen
acx565akm_check_timings(struct omap_dss_device * dssdev,struct omap_video_timings * timings)673f76ee892STomi Valkeinen static int acx565akm_check_timings(struct omap_dss_device *dssdev,
674f76ee892STomi Valkeinen struct omap_video_timings *timings)
675f76ee892STomi Valkeinen {
676f76ee892STomi Valkeinen struct panel_drv_data *ddata = to_panel_data(dssdev);
677f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in;
678f76ee892STomi Valkeinen
679f76ee892STomi Valkeinen return in->ops.sdi->check_timings(in, timings);
680f76ee892STomi Valkeinen }
681f76ee892STomi Valkeinen
682f76ee892STomi Valkeinen static struct omap_dss_driver acx565akm_ops = {
683f76ee892STomi Valkeinen .connect = acx565akm_connect,
684f76ee892STomi Valkeinen .disconnect = acx565akm_disconnect,
685f76ee892STomi Valkeinen
686f76ee892STomi Valkeinen .enable = acx565akm_enable,
687f76ee892STomi Valkeinen .disable = acx565akm_disable,
688f76ee892STomi Valkeinen
689f76ee892STomi Valkeinen .set_timings = acx565akm_set_timings,
690f76ee892STomi Valkeinen .get_timings = acx565akm_get_timings,
691f76ee892STomi Valkeinen .check_timings = acx565akm_check_timings,
692f76ee892STomi Valkeinen
693f76ee892STomi Valkeinen .get_resolution = omapdss_default_get_resolution,
694f76ee892STomi Valkeinen };
695f76ee892STomi Valkeinen
acx565akm_probe(struct spi_device * spi)696f76ee892STomi Valkeinen static int acx565akm_probe(struct spi_device *spi)
697f76ee892STomi Valkeinen {
698f76ee892STomi Valkeinen struct panel_drv_data *ddata;
699f76ee892STomi Valkeinen struct omap_dss_device *dssdev;
700f76ee892STomi Valkeinen struct backlight_device *bldev;
701f76ee892STomi Valkeinen int max_brightness, brightness;
702f76ee892STomi Valkeinen struct backlight_properties props;
703f76ee892STomi Valkeinen int r;
704f76ee892STomi Valkeinen
705f76ee892STomi Valkeinen dev_dbg(&spi->dev, "%s\n", __func__);
706f76ee892STomi Valkeinen
7075845b32eSDmitry Torokhov if (!spi->dev.of_node)
7085845b32eSDmitry Torokhov return -ENODEV;
7095845b32eSDmitry Torokhov
710f76ee892STomi Valkeinen spi->mode = SPI_MODE_3;
711f76ee892STomi Valkeinen
712f76ee892STomi Valkeinen ddata = devm_kzalloc(&spi->dev, sizeof(*ddata), GFP_KERNEL);
713f76ee892STomi Valkeinen if (ddata == NULL)
714f76ee892STomi Valkeinen return -ENOMEM;
715f76ee892STomi Valkeinen
716f76ee892STomi Valkeinen dev_set_drvdata(&spi->dev, ddata);
717f76ee892STomi Valkeinen
718f76ee892STomi Valkeinen ddata->spi = spi;
719f76ee892STomi Valkeinen
720f76ee892STomi Valkeinen mutex_init(&ddata->mutex);
721f76ee892STomi Valkeinen
722*844c245fSDmitry Torokhov ddata->in = omapdss_of_find_source_for_first_ep(spi->dev.of_node);
723*844c245fSDmitry Torokhov r = PTR_ERR_OR_ZERO(ddata->in);
724*844c245fSDmitry Torokhov if (r) {
725*844c245fSDmitry Torokhov dev_err(&spi->dev, "failed to find video source\n");
726f76ee892STomi Valkeinen return r;
727f76ee892STomi Valkeinen }
728f76ee892STomi Valkeinen
729*844c245fSDmitry Torokhov ddata->reset_gpio = devm_gpiod_get_optional(&spi->dev, "reset",
730*844c245fSDmitry Torokhov GPIOD_OUT_LOW);
731*844c245fSDmitry Torokhov r = PTR_ERR_OR_ZERO(ddata->reset_gpio);
732*844c245fSDmitry Torokhov if (r)
733*844c245fSDmitry Torokhov goto err_gpio;
734*844c245fSDmitry Torokhov
735*844c245fSDmitry Torokhov if (ddata->reset_gpio) {
736*844c245fSDmitry Torokhov gpiod_set_consumer_name(ddata->reset_gpio, "lcd reset");
737*844c245fSDmitry Torokhov
738*844c245fSDmitry Torokhov /* release the reset line */
739*844c245fSDmitry Torokhov gpiod_set_value_cansleep(ddata->reset_gpio, 1);
740*844c245fSDmitry Torokhov }
741f76ee892STomi Valkeinen
742f76ee892STomi Valkeinen /*
743f76ee892STomi Valkeinen * After reset we have to wait 5 msec before the first
744f76ee892STomi Valkeinen * command can be sent.
745f76ee892STomi Valkeinen */
746f76ee892STomi Valkeinen usleep_range(5000, 10000);
747f76ee892STomi Valkeinen
748f76ee892STomi Valkeinen ddata->enabled = panel_enabled(ddata);
749f76ee892STomi Valkeinen
750f76ee892STomi Valkeinen r = panel_detect(ddata);
751f76ee892STomi Valkeinen
752*844c245fSDmitry Torokhov if (!ddata->enabled && ddata->reset_gpio)
753*844c245fSDmitry Torokhov gpiod_set_value_cansleep(ddata->reset_gpio, 0);
754f76ee892STomi Valkeinen
755f76ee892STomi Valkeinen if (r) {
756f76ee892STomi Valkeinen dev_err(&spi->dev, "%s panel detect error\n", __func__);
757f76ee892STomi Valkeinen goto err_detect;
758f76ee892STomi Valkeinen }
759f76ee892STomi Valkeinen
760f76ee892STomi Valkeinen memset(&props, 0, sizeof(props));
761f76ee892STomi Valkeinen props.fb_blank = FB_BLANK_UNBLANK;
762f76ee892STomi Valkeinen props.power = FB_BLANK_UNBLANK;
763f76ee892STomi Valkeinen props.type = BACKLIGHT_RAW;
764f76ee892STomi Valkeinen
765f76ee892STomi Valkeinen bldev = backlight_device_register("acx565akm", &ddata->spi->dev,
766f76ee892STomi Valkeinen ddata, &acx565akm_bl_ops, &props);
767f76ee892STomi Valkeinen if (IS_ERR(bldev)) {
768f76ee892STomi Valkeinen r = PTR_ERR(bldev);
769f76ee892STomi Valkeinen goto err_reg_bl;
770f76ee892STomi Valkeinen }
771f76ee892STomi Valkeinen ddata->bl_dev = bldev;
772f76ee892STomi Valkeinen if (ddata->has_cabc) {
773f76ee892STomi Valkeinen r = sysfs_create_group(&bldev->dev.kobj, &bldev_attr_group);
774f76ee892STomi Valkeinen if (r) {
775f76ee892STomi Valkeinen dev_err(&bldev->dev,
776f76ee892STomi Valkeinen "%s failed to create sysfs files\n", __func__);
777f76ee892STomi Valkeinen goto err_sysfs;
778f76ee892STomi Valkeinen }
779f76ee892STomi Valkeinen ddata->cabc_mode = get_hw_cabc_mode(ddata);
780f76ee892STomi Valkeinen }
781f76ee892STomi Valkeinen
782f76ee892STomi Valkeinen max_brightness = 255;
783f76ee892STomi Valkeinen
784f76ee892STomi Valkeinen if (ddata->has_bc)
785f76ee892STomi Valkeinen brightness = acx565akm_get_actual_brightness(ddata);
786f76ee892STomi Valkeinen else
787f76ee892STomi Valkeinen brightness = 0;
788f76ee892STomi Valkeinen
789f76ee892STomi Valkeinen bldev->props.max_brightness = max_brightness;
790f76ee892STomi Valkeinen bldev->props.brightness = brightness;
791f76ee892STomi Valkeinen
792f76ee892STomi Valkeinen acx565akm_bl_update_status(bldev);
793f76ee892STomi Valkeinen
794f76ee892STomi Valkeinen
795f76ee892STomi Valkeinen ddata->videomode = acx565akm_panel_timings;
796f76ee892STomi Valkeinen
797f76ee892STomi Valkeinen dssdev = &ddata->dssdev;
798f76ee892STomi Valkeinen dssdev->dev = &spi->dev;
799f76ee892STomi Valkeinen dssdev->driver = &acx565akm_ops;
800f76ee892STomi Valkeinen dssdev->type = OMAP_DISPLAY_TYPE_SDI;
801f76ee892STomi Valkeinen dssdev->owner = THIS_MODULE;
802f76ee892STomi Valkeinen dssdev->panel.timings = ddata->videomode;
803f76ee892STomi Valkeinen
804f76ee892STomi Valkeinen r = omapdss_register_display(dssdev);
805f76ee892STomi Valkeinen if (r) {
806f76ee892STomi Valkeinen dev_err(&spi->dev, "Failed to register panel\n");
807f76ee892STomi Valkeinen goto err_reg;
808f76ee892STomi Valkeinen }
809f76ee892STomi Valkeinen
810f76ee892STomi Valkeinen return 0;
811f76ee892STomi Valkeinen
812f76ee892STomi Valkeinen err_reg:
813f76ee892STomi Valkeinen sysfs_remove_group(&bldev->dev.kobj, &bldev_attr_group);
814f76ee892STomi Valkeinen err_sysfs:
815f76ee892STomi Valkeinen backlight_device_unregister(bldev);
816f76ee892STomi Valkeinen err_reg_bl:
817f76ee892STomi Valkeinen err_detect:
818f76ee892STomi Valkeinen err_gpio:
819f76ee892STomi Valkeinen omap_dss_put_device(ddata->in);
820f76ee892STomi Valkeinen return r;
821f76ee892STomi Valkeinen }
822f76ee892STomi Valkeinen
acx565akm_remove(struct spi_device * spi)823a0386bbaSUwe Kleine-König static void acx565akm_remove(struct spi_device *spi)
824f76ee892STomi Valkeinen {
825f76ee892STomi Valkeinen struct panel_drv_data *ddata = dev_get_drvdata(&spi->dev);
826f76ee892STomi Valkeinen struct omap_dss_device *dssdev = &ddata->dssdev;
827f76ee892STomi Valkeinen struct omap_dss_device *in = ddata->in;
828f76ee892STomi Valkeinen
829f76ee892STomi Valkeinen dev_dbg(&ddata->spi->dev, "%s\n", __func__);
830f76ee892STomi Valkeinen
831f76ee892STomi Valkeinen sysfs_remove_group(&ddata->bl_dev->dev.kobj, &bldev_attr_group);
832f76ee892STomi Valkeinen backlight_device_unregister(ddata->bl_dev);
833f76ee892STomi Valkeinen
834f76ee892STomi Valkeinen omapdss_unregister_display(dssdev);
835f76ee892STomi Valkeinen
836f76ee892STomi Valkeinen acx565akm_disable(dssdev);
837f76ee892STomi Valkeinen acx565akm_disconnect(dssdev);
838f76ee892STomi Valkeinen
839f76ee892STomi Valkeinen omap_dss_put_device(in);
840f76ee892STomi Valkeinen }
841f76ee892STomi Valkeinen
842f76ee892STomi Valkeinen static const struct of_device_id acx565akm_of_match[] = {
843f76ee892STomi Valkeinen { .compatible = "omapdss,sony,acx565akm", },
844f76ee892STomi Valkeinen {},
845f76ee892STomi Valkeinen };
846f76ee892STomi Valkeinen MODULE_DEVICE_TABLE(of, acx565akm_of_match);
847f76ee892STomi Valkeinen
848f76ee892STomi Valkeinen static struct spi_driver acx565akm_driver = {
849f76ee892STomi Valkeinen .driver = {
850f76ee892STomi Valkeinen .name = "acx565akm",
851f76ee892STomi Valkeinen .of_match_table = acx565akm_of_match,
852f76ee892STomi Valkeinen .suppress_bind_attrs = true,
853f76ee892STomi Valkeinen },
854f76ee892STomi Valkeinen .probe = acx565akm_probe,
855f76ee892STomi Valkeinen .remove = acx565akm_remove,
856f76ee892STomi Valkeinen };
857f76ee892STomi Valkeinen
858f76ee892STomi Valkeinen module_spi_driver(acx565akm_driver);
859f76ee892STomi Valkeinen
860f76ee892STomi Valkeinen MODULE_AUTHOR("Nokia Corporation");
861f76ee892STomi Valkeinen MODULE_DESCRIPTION("acx565akm LCD Driver");
862f76ee892STomi Valkeinen MODULE_LICENSE("GPL");
863