xref: /linux/drivers/gpu/drm/sitronix/st7571.c (revision c17ee635fd3a482b2ad2bf5e269755c2eae5f25e)
1*b362de16SMarcus Folkesson // SPDX-License-Identifier: GPL-2.0-or-later
2*b362de16SMarcus Folkesson /*
3*b362de16SMarcus Folkesson  * Driver for Sitronix ST7571, a 4 level gray scale dot matrix LCD controller
4*b362de16SMarcus Folkesson  *
5*b362de16SMarcus Folkesson  * Copyright (C) 2025 Marcus Folkesson <marcus.folkesson@gmail.com>
6*b362de16SMarcus Folkesson  */
7*b362de16SMarcus Folkesson 
8*b362de16SMarcus Folkesson #include <linux/bitfield.h>
9*b362de16SMarcus Folkesson #include <linux/delay.h>
10*b362de16SMarcus Folkesson #include <linux/gpio/consumer.h>
11*b362de16SMarcus Folkesson #include <linux/i2c.h>
12*b362de16SMarcus Folkesson #include <linux/module.h>
13*b362de16SMarcus Folkesson #include <linux/regmap.h>
14*b362de16SMarcus Folkesson 
15*b362de16SMarcus Folkesson #include <drm/clients/drm_client_setup.h>
16*b362de16SMarcus Folkesson #include <drm/drm_atomic.h>
17*b362de16SMarcus Folkesson #include <drm/drm_atomic_helper.h>
18*b362de16SMarcus Folkesson #include <drm/drm_connector.h>
19*b362de16SMarcus Folkesson #include <drm/drm_crtc_helper.h>
20*b362de16SMarcus Folkesson #include <drm/drm_damage_helper.h>
21*b362de16SMarcus Folkesson #include <drm/drm_drv.h>
22*b362de16SMarcus Folkesson #include <drm/drm_encoder.h>
23*b362de16SMarcus Folkesson #include <drm/drm_fb_helper.h>
24*b362de16SMarcus Folkesson #include <drm/drm_fbdev_shmem.h>
25*b362de16SMarcus Folkesson #include <drm/drm_fourcc.h>
26*b362de16SMarcus Folkesson #include <drm/drm_framebuffer.h>
27*b362de16SMarcus Folkesson #include <drm/drm_gem_atomic_helper.h>
28*b362de16SMarcus Folkesson #include <drm/drm_gem_framebuffer_helper.h>
29*b362de16SMarcus Folkesson #include <drm/drm_gem_shmem_helper.h>
30*b362de16SMarcus Folkesson #include <drm/drm_modeset_helper_vtables.h>
31*b362de16SMarcus Folkesson #include <drm/drm_module.h>
32*b362de16SMarcus Folkesson #include <drm/drm_plane.h>
33*b362de16SMarcus Folkesson #include <drm/drm_probe_helper.h>
34*b362de16SMarcus Folkesson 
35*b362de16SMarcus Folkesson #include <video/display_timing.h>
36*b362de16SMarcus Folkesson #include <video/of_display_timing.h>
37*b362de16SMarcus Folkesson 
38*b362de16SMarcus Folkesson #include "st7571.h"
39*b362de16SMarcus Folkesson 
40*b362de16SMarcus Folkesson #define ST7571_COMMAND_MODE			(0x00)
41*b362de16SMarcus Folkesson #define ST7571_DATA_MODE			(0x40)
42*b362de16SMarcus Folkesson 
43*b362de16SMarcus Folkesson /* Normal mode command set */
44*b362de16SMarcus Folkesson #define ST7571_DISPLAY_OFF			(0xae)
45*b362de16SMarcus Folkesson #define ST7571_DISPLAY_ON			(0xaf)
46*b362de16SMarcus Folkesson #define ST7571_OSC_ON				(0xab)
47*b362de16SMarcus Folkesson #define ST7571_SET_COLUMN_LSB(c)		(0x00 | FIELD_PREP(GENMASK(3, 0), (c)))
48*b362de16SMarcus Folkesson #define ST7571_SET_COLUMN_MSB(c)		(0x10 | FIELD_PREP(GENMASK(2, 0), (c) >> 4))
49*b362de16SMarcus Folkesson #define ST7571_SET_COM0_LSB(x)			(FIELD_PREP(GENMASK(6, 0), (x)))
50*b362de16SMarcus Folkesson #define ST7571_SET_COM0_MSB			(0x44)
51*b362de16SMarcus Folkesson #define ST7571_SET_COM_SCAN_DIR(d)		(0xc0 | FIELD_PREP(GENMASK(3, 3), (d)))
52*b362de16SMarcus Folkesson #define ST7571_SET_CONTRAST_LSB(c)		(FIELD_PREP(GENMASK(5, 0), (c)))
53*b362de16SMarcus Folkesson #define ST7571_SET_CONTRAST_MSB			(0x81)
54*b362de16SMarcus Folkesson #define ST7571_SET_DISPLAY_DUTY_LSB(d)		(FIELD_PREP(GENMASK(7, 0), (d)))
55*b362de16SMarcus Folkesson #define ST7571_SET_DISPLAY_DUTY_MSB		(0x48)
56*b362de16SMarcus Folkesson #define ST7571_SET_ENTIRE_DISPLAY_ON(p)		(0xa4 | FIELD_PREP(GENMASK(0, 0), (p)))
57*b362de16SMarcus Folkesson #define ST7571_SET_LCD_BIAS(b)			(0x50 | FIELD_PREP(GENMASK(2, 0), (b)))
58*b362de16SMarcus Folkesson #define ST7571_SET_MODE_LSB(m)			(FIELD_PREP(GENMASK(7, 2), (m)))
59*b362de16SMarcus Folkesson #define ST7571_SET_MODE_MSB			(0x38)
60*b362de16SMarcus Folkesson #define ST7571_SET_PAGE(p)			(0xb0 | FIELD_PREP(GENMASK(3, 0), (p)))
61*b362de16SMarcus Folkesson #define ST7571_SET_POWER(p)			(0x28 | FIELD_PREP(GENMASK(2, 0), (p)))
62*b362de16SMarcus Folkesson #define ST7571_SET_REGULATOR_REG(r)		(0x20 | FIELD_PREP(GENMASK(2, 0), (r)))
63*b362de16SMarcus Folkesson #define ST7571_SET_REVERSE(r)			(0xa6 | FIELD_PREP(GENMASK(0, 0), (r)))
64*b362de16SMarcus Folkesson #define ST7571_SET_SEG_SCAN_DIR(d)		(0xa0 | FIELD_PREP(GENMASK(0, 0), (d)))
65*b362de16SMarcus Folkesson #define ST7571_SET_START_LINE_LSB(l)		(FIELD_PREP(GENMASK(6, 0), (l)))
66*b362de16SMarcus Folkesson #define ST7571_SET_START_LINE_MSB		(0x40)
67*b362de16SMarcus Folkesson 
68*b362de16SMarcus Folkesson /* Extension command set 3 */
69*b362de16SMarcus Folkesson #define ST7571_COMMAND_SET_3			(0x7b)
70*b362de16SMarcus Folkesson #define ST7571_SET_COLOR_MODE(c)		(0x10 | FIELD_PREP(GENMASK(0, 0), (c)))
71*b362de16SMarcus Folkesson #define ST7571_COMMAND_SET_NORMAL		(0x00)
72*b362de16SMarcus Folkesson 
73*b362de16SMarcus Folkesson /* ST7567 commands */
74*b362de16SMarcus Folkesson #define ST7567_SET_LCD_BIAS(m) (0xa2 | FIELD_PREP(GENMASK(0, 0), (m)))
75*b362de16SMarcus Folkesson 
76*b362de16SMarcus Folkesson #define ST7571_PAGE_HEIGHT 8
77*b362de16SMarcus Folkesson 
78*b362de16SMarcus Folkesson #define DRIVER_NAME "st7571"
79*b362de16SMarcus Folkesson #define DRIVER_DESC "ST7571 DRM driver"
80*b362de16SMarcus Folkesson #define DRIVER_MAJOR 1
81*b362de16SMarcus Folkesson #define DRIVER_MINOR 0
82*b362de16SMarcus Folkesson 
83*b362de16SMarcus Folkesson static inline struct st7571_device *drm_to_st7571(struct drm_device *drm)
84*b362de16SMarcus Folkesson {
85*b362de16SMarcus Folkesson 	return container_of(drm, struct st7571_device, drm);
86*b362de16SMarcus Folkesson }
87*b362de16SMarcus Folkesson 
88*b362de16SMarcus Folkesson static int st7571_send_command_list(struct st7571_device *st7571,
89*b362de16SMarcus Folkesson 				    const u8 *cmd_list, size_t len)
90*b362de16SMarcus Folkesson {
91*b362de16SMarcus Folkesson 	int ret;
92*b362de16SMarcus Folkesson 
93*b362de16SMarcus Folkesson 	for (int i = 0; i < len; i++) {
94*b362de16SMarcus Folkesson 		ret = regmap_write(st7571->regmap, ST7571_COMMAND_MODE, cmd_list[i]);
95*b362de16SMarcus Folkesson 		if (ret < 0)
96*b362de16SMarcus Folkesson 			return ret;
97*b362de16SMarcus Folkesson 	}
98*b362de16SMarcus Folkesson 
99*b362de16SMarcus Folkesson 	return ret;
100*b362de16SMarcus Folkesson }
101*b362de16SMarcus Folkesson 
102*b362de16SMarcus Folkesson static inline u8 st7571_transform_xy(const char *p, int x, int y, u8 bpp)
103*b362de16SMarcus Folkesson {
104*b362de16SMarcus Folkesson 	int xrest = x % 8;
105*b362de16SMarcus Folkesson 	u8 result = 0;
106*b362de16SMarcus Folkesson 	u8 row_len = 16 * bpp;
107*b362de16SMarcus Folkesson 
108*b362de16SMarcus Folkesson 	/*
109*b362de16SMarcus Folkesson 	 * Transforms an (x, y) pixel coordinate into a vertical 8-bit
110*b362de16SMarcus Folkesson 	 * column from the framebuffer. It calculates the corresponding byte in the
111*b362de16SMarcus Folkesson 	 * framebuffer, extracts the bit at the given x position across 8 consecutive
112*b362de16SMarcus Folkesson 	 * rows, and packs those bits into a single byte.
113*b362de16SMarcus Folkesson 	 *
114*b362de16SMarcus Folkesson 	 * Return an 8-bit value representing a vertical column of pixels.
115*b362de16SMarcus Folkesson 	 */
116*b362de16SMarcus Folkesson 	x = x / 8;
117*b362de16SMarcus Folkesson 	y = (y / 8) * 8;
118*b362de16SMarcus Folkesson 
119*b362de16SMarcus Folkesson 	for (int i = 0; i < 8; i++) {
120*b362de16SMarcus Folkesson 		int row_idx = y + i;
121*b362de16SMarcus Folkesson 		u8 byte = p[row_idx * row_len + x];
122*b362de16SMarcus Folkesson 		u8 bit = (byte >> xrest) & 1;
123*b362de16SMarcus Folkesson 
124*b362de16SMarcus Folkesson 		result |= (bit << i);
125*b362de16SMarcus Folkesson 	}
126*b362de16SMarcus Folkesson 
127*b362de16SMarcus Folkesson 	return result;
128*b362de16SMarcus Folkesson }
129*b362de16SMarcus Folkesson 
130*b362de16SMarcus Folkesson static int st7571_set_position(struct st7571_device *st7571, int x, int y)
131*b362de16SMarcus Folkesson {
132*b362de16SMarcus Folkesson 	u8 cmd_list[] = {
133*b362de16SMarcus Folkesson 		ST7571_SET_COLUMN_LSB(x),
134*b362de16SMarcus Folkesson 		ST7571_SET_COLUMN_MSB(x),
135*b362de16SMarcus Folkesson 		ST7571_SET_PAGE(y / ST7571_PAGE_HEIGHT),
136*b362de16SMarcus Folkesson 	};
137*b362de16SMarcus Folkesson 
138*b362de16SMarcus Folkesson 	return st7571_send_command_list(st7571, cmd_list, ARRAY_SIZE(cmd_list));
139*b362de16SMarcus Folkesson }
140*b362de16SMarcus Folkesson 
141*b362de16SMarcus Folkesson static int st7571_fb_clear_screen(struct st7571_device *st7571)
142*b362de16SMarcus Folkesson {
143*b362de16SMarcus Folkesson 	u32 npixels = st7571->ncols * round_up(st7571->nlines, ST7571_PAGE_HEIGHT) * st7571->bpp;
144*b362de16SMarcus Folkesson 	char pixelvalue = 0x00;
145*b362de16SMarcus Folkesson 
146*b362de16SMarcus Folkesson 	st7571_set_position(st7571, 0, 0);
147*b362de16SMarcus Folkesson 	for (int i = 0; i < npixels; i++)
148*b362de16SMarcus Folkesson 		regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, &pixelvalue, 1);
149*b362de16SMarcus Folkesson 
150*b362de16SMarcus Folkesson 	return 0;
151*b362de16SMarcus Folkesson }
152*b362de16SMarcus Folkesson 
153*b362de16SMarcus Folkesson static void st7571_prepare_buffer_monochrome(struct st7571_device *st7571,
154*b362de16SMarcus Folkesson 					     const struct iosys_map *vmap,
155*b362de16SMarcus Folkesson 					     struct drm_framebuffer *fb,
156*b362de16SMarcus Folkesson 					     struct drm_rect *rect,
157*b362de16SMarcus Folkesson 					     struct drm_format_conv_state *fmtcnv_state)
158*b362de16SMarcus Folkesson {
159*b362de16SMarcus Folkesson 	unsigned int dst_pitch;
160*b362de16SMarcus Folkesson 	struct iosys_map dst;
161*b362de16SMarcus Folkesson 	u32 size;
162*b362de16SMarcus Folkesson 
163*b362de16SMarcus Folkesson 	switch (fb->format->format) {
164*b362de16SMarcus Folkesson 	case DRM_FORMAT_XRGB8888:
165*b362de16SMarcus Folkesson 		dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 8);
166*b362de16SMarcus Folkesson 		iosys_map_set_vaddr(&dst, st7571->hwbuf);
167*b362de16SMarcus Folkesson 
168*b362de16SMarcus Folkesson 		drm_fb_xrgb8888_to_mono(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state);
169*b362de16SMarcus Folkesson 		break;
170*b362de16SMarcus Folkesson 
171*b362de16SMarcus Folkesson 	case DRM_FORMAT_R1:
172*b362de16SMarcus Folkesson 		size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8;
173*b362de16SMarcus Folkesson 		memcpy(st7571->hwbuf, vmap->vaddr, size);
174*b362de16SMarcus Folkesson 		break;
175*b362de16SMarcus Folkesson 	}
176*b362de16SMarcus Folkesson }
177*b362de16SMarcus Folkesson 
178*b362de16SMarcus Folkesson static void st7571_prepare_buffer_grayscale(struct st7571_device *st7571,
179*b362de16SMarcus Folkesson 					    const struct iosys_map *vmap,
180*b362de16SMarcus Folkesson 					    struct drm_framebuffer *fb,
181*b362de16SMarcus Folkesson 					    struct drm_rect *rect,
182*b362de16SMarcus Folkesson 					    struct drm_format_conv_state *fmtcnv_state)
183*b362de16SMarcus Folkesson {
184*b362de16SMarcus Folkesson 	u32 size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8;
185*b362de16SMarcus Folkesson 	unsigned int dst_pitch;
186*b362de16SMarcus Folkesson 	struct iosys_map dst;
187*b362de16SMarcus Folkesson 
188*b362de16SMarcus Folkesson 	switch (fb->format->format) {
189*b362de16SMarcus Folkesson 	case DRM_FORMAT_XRGB8888:
190*b362de16SMarcus Folkesson 		dst_pitch = DIV_ROUND_UP(drm_rect_width(rect), 4);
191*b362de16SMarcus Folkesson 		iosys_map_set_vaddr(&dst, st7571->hwbuf);
192*b362de16SMarcus Folkesson 
193*b362de16SMarcus Folkesson 		drm_fb_xrgb8888_to_gray2(&dst, &dst_pitch, vmap, fb, rect, fmtcnv_state);
194*b362de16SMarcus Folkesson 		break;
195*b362de16SMarcus Folkesson 
196*b362de16SMarcus Folkesson 	case DRM_FORMAT_R1:
197*b362de16SMarcus Folkesson 		size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 8;
198*b362de16SMarcus Folkesson 		memcpy(st7571->hwbuf, vmap->vaddr, size);
199*b362de16SMarcus Folkesson 		break;
200*b362de16SMarcus Folkesson 
201*b362de16SMarcus Folkesson 	case DRM_FORMAT_R2:
202*b362de16SMarcus Folkesson 		size = (rect->x2 - rect->x1) * (rect->y2 - rect->y1) / 4;
203*b362de16SMarcus Folkesson 		memcpy(st7571->hwbuf, vmap->vaddr, size);
204*b362de16SMarcus Folkesson 		break;
205*b362de16SMarcus Folkesson 	}
206*b362de16SMarcus Folkesson }
207*b362de16SMarcus Folkesson 
208*b362de16SMarcus Folkesson static int st7571_fb_update_rect_monochrome(struct drm_framebuffer *fb, struct drm_rect *rect)
209*b362de16SMarcus Folkesson {
210*b362de16SMarcus Folkesson 	struct st7571_device *st7571 = drm_to_st7571(fb->dev);
211*b362de16SMarcus Folkesson 	char *row = st7571->row;
212*b362de16SMarcus Folkesson 
213*b362de16SMarcus Folkesson 	/* Align y to display page boundaries */
214*b362de16SMarcus Folkesson 	rect->y1 = round_down(rect->y1, ST7571_PAGE_HEIGHT);
215*b362de16SMarcus Folkesson 	rect->y2 = min_t(unsigned int, round_up(rect->y2, ST7571_PAGE_HEIGHT), st7571->nlines);
216*b362de16SMarcus Folkesson 
217*b362de16SMarcus Folkesson 	for (int y = rect->y1; y < rect->y2; y += ST7571_PAGE_HEIGHT) {
218*b362de16SMarcus Folkesson 		for (int x = rect->x1; x < rect->x2; x++)
219*b362de16SMarcus Folkesson 			row[x] = st7571_transform_xy(st7571->hwbuf, x, y, 1);
220*b362de16SMarcus Folkesson 
221*b362de16SMarcus Folkesson 		st7571_set_position(st7571, rect->x1, y);
222*b362de16SMarcus Folkesson 
223*b362de16SMarcus Folkesson 		/* TODO: Investige why we can't write multiple bytes at once */
224*b362de16SMarcus Folkesson 		for (int x = rect->x1; x < rect->x2; x++)
225*b362de16SMarcus Folkesson 			regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1);
226*b362de16SMarcus Folkesson 	}
227*b362de16SMarcus Folkesson 
228*b362de16SMarcus Folkesson 	return 0;
229*b362de16SMarcus Folkesson }
230*b362de16SMarcus Folkesson 
231*b362de16SMarcus Folkesson static int st7571_fb_update_rect_grayscale(struct drm_framebuffer *fb, struct drm_rect *rect)
232*b362de16SMarcus Folkesson {
233*b362de16SMarcus Folkesson 	struct st7571_device *st7571 = drm_to_st7571(fb->dev);
234*b362de16SMarcus Folkesson 	u32 format = fb->format->format;
235*b362de16SMarcus Folkesson 	char *row = st7571->row;
236*b362de16SMarcus Folkesson 	int x1;
237*b362de16SMarcus Folkesson 	int x2;
238*b362de16SMarcus Folkesson 
239*b362de16SMarcus Folkesson 	/* Align y to display page boundaries */
240*b362de16SMarcus Folkesson 	rect->y1 = round_down(rect->y1, ST7571_PAGE_HEIGHT);
241*b362de16SMarcus Folkesson 	rect->y2 = min_t(unsigned int, round_up(rect->y2, ST7571_PAGE_HEIGHT), st7571->nlines);
242*b362de16SMarcus Folkesson 
243*b362de16SMarcus Folkesson 	switch (format) {
244*b362de16SMarcus Folkesson 	case DRM_FORMAT_R1:
245*b362de16SMarcus Folkesson 		x1 = rect->x1 * 1;
246*b362de16SMarcus Folkesson 		x2 = rect->x2 * 1;
247*b362de16SMarcus Folkesson 		break;
248*b362de16SMarcus Folkesson 	case DRM_FORMAT_R2:
249*b362de16SMarcus Folkesson 		fallthrough;
250*b362de16SMarcus Folkesson 	case DRM_FORMAT_XRGB8888:
251*b362de16SMarcus Folkesson 		x1 = rect->x1 * 2;
252*b362de16SMarcus Folkesson 		x2 = rect->x2 * 2;
253*b362de16SMarcus Folkesson 		break;
254*b362de16SMarcus Folkesson 	}
255*b362de16SMarcus Folkesson 
256*b362de16SMarcus Folkesson 	for (int y = rect->y1; y < rect->y2; y += ST7571_PAGE_HEIGHT) {
257*b362de16SMarcus Folkesson 		for (int x = x1; x < x2; x++)
258*b362de16SMarcus Folkesson 			row[x] = st7571_transform_xy(st7571->hwbuf, x, y, 2);
259*b362de16SMarcus Folkesson 
260*b362de16SMarcus Folkesson 		st7571_set_position(st7571, rect->x1, y);
261*b362de16SMarcus Folkesson 
262*b362de16SMarcus Folkesson 		/* TODO: Investige why we can't write multiple bytes at once */
263*b362de16SMarcus Folkesson 		for (int x = x1; x < x2; x++) {
264*b362de16SMarcus Folkesson 			regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1);
265*b362de16SMarcus Folkesson 
266*b362de16SMarcus Folkesson 			/*
267*b362de16SMarcus Folkesson 			 * As the display supports grayscale, all pixels must be written as two bits
268*b362de16SMarcus Folkesson 			 * even if the format is monochrome.
269*b362de16SMarcus Folkesson 			 *
270*b362de16SMarcus Folkesson 			 * The bit values maps to the following grayscale:
271*b362de16SMarcus Folkesson 			 * 0 0 = Black
272*b362de16SMarcus Folkesson 			 * 0 1 = Dark gray
273*b362de16SMarcus Folkesson 			 * 1 0 = Light gray
274*b362de16SMarcus Folkesson 			 * 1 1 = White
275*b362de16SMarcus Folkesson 			 *
276*b362de16SMarcus Folkesson 			 * For monochrome formats, write the same value twice to get
277*b362de16SMarcus Folkesson 			 * either a black or white pixel.
278*b362de16SMarcus Folkesson 			 */
279*b362de16SMarcus Folkesson 			if (format == DRM_FORMAT_R1)
280*b362de16SMarcus Folkesson 				regmap_bulk_write(st7571->regmap, ST7571_DATA_MODE, row + x, 1);
281*b362de16SMarcus Folkesson 		}
282*b362de16SMarcus Folkesson 	}
283*b362de16SMarcus Folkesson 
284*b362de16SMarcus Folkesson 	return 0;
285*b362de16SMarcus Folkesson }
286*b362de16SMarcus Folkesson 
287*b362de16SMarcus Folkesson static int st7571_connector_get_modes(struct drm_connector *conn)
288*b362de16SMarcus Folkesson {
289*b362de16SMarcus Folkesson 	struct st7571_device *st7571 = drm_to_st7571(conn->dev);
290*b362de16SMarcus Folkesson 
291*b362de16SMarcus Folkesson 	return drm_connector_helper_get_modes_fixed(conn, &st7571->mode);
292*b362de16SMarcus Folkesson }
293*b362de16SMarcus Folkesson 
294*b362de16SMarcus Folkesson static const struct drm_connector_helper_funcs st7571_connector_helper_funcs = {
295*b362de16SMarcus Folkesson 	.get_modes = st7571_connector_get_modes,
296*b362de16SMarcus Folkesson };
297*b362de16SMarcus Folkesson 
298*b362de16SMarcus Folkesson static const struct st7571_panel_format st7571_monochrome = {
299*b362de16SMarcus Folkesson 	.prepare_buffer = st7571_prepare_buffer_monochrome,
300*b362de16SMarcus Folkesson 	.update_rect = st7571_fb_update_rect_monochrome,
301*b362de16SMarcus Folkesson 	.mode = ST7571_COLOR_MODE_BLACKWHITE,
302*b362de16SMarcus Folkesson 	.formats = {
303*b362de16SMarcus Folkesson 		DRM_FORMAT_XRGB8888,
304*b362de16SMarcus Folkesson 		DRM_FORMAT_R1,
305*b362de16SMarcus Folkesson 	},
306*b362de16SMarcus Folkesson 	.nformats = 2,
307*b362de16SMarcus Folkesson };
308*b362de16SMarcus Folkesson 
309*b362de16SMarcus Folkesson static const struct st7571_panel_format st7571_grayscale = {
310*b362de16SMarcus Folkesson 	.prepare_buffer = st7571_prepare_buffer_grayscale,
311*b362de16SMarcus Folkesson 	.update_rect = st7571_fb_update_rect_grayscale,
312*b362de16SMarcus Folkesson 	.mode = ST7571_COLOR_MODE_GRAY,
313*b362de16SMarcus Folkesson 	.formats = {
314*b362de16SMarcus Folkesson 		DRM_FORMAT_XRGB8888,
315*b362de16SMarcus Folkesson 		DRM_FORMAT_R1,
316*b362de16SMarcus Folkesson 		DRM_FORMAT_R2,
317*b362de16SMarcus Folkesson 	},
318*b362de16SMarcus Folkesson 	.nformats = 3,
319*b362de16SMarcus Folkesson };
320*b362de16SMarcus Folkesson 
321*b362de16SMarcus Folkesson static const u64 st7571_primary_plane_fmtmods[] = {
322*b362de16SMarcus Folkesson 	DRM_FORMAT_MOD_LINEAR,
323*b362de16SMarcus Folkesson 	DRM_FORMAT_MOD_INVALID
324*b362de16SMarcus Folkesson };
325*b362de16SMarcus Folkesson 
326*b362de16SMarcus Folkesson static int st7571_primary_plane_helper_atomic_check(struct drm_plane *plane,
327*b362de16SMarcus Folkesson 						    struct drm_atomic_state *state)
328*b362de16SMarcus Folkesson {
329*b362de16SMarcus Folkesson 	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane);
330*b362de16SMarcus Folkesson 	struct drm_crtc *new_crtc = new_plane_state->crtc;
331*b362de16SMarcus Folkesson 	struct drm_crtc_state *new_crtc_state = NULL;
332*b362de16SMarcus Folkesson 
333*b362de16SMarcus Folkesson 	if (new_crtc)
334*b362de16SMarcus Folkesson 		new_crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc);
335*b362de16SMarcus Folkesson 
336*b362de16SMarcus Folkesson 	return drm_atomic_helper_check_plane_state(new_plane_state, new_crtc_state,
337*b362de16SMarcus Folkesson 						   DRM_PLANE_NO_SCALING,
338*b362de16SMarcus Folkesson 						   DRM_PLANE_NO_SCALING,
339*b362de16SMarcus Folkesson 						   false, false);
340*b362de16SMarcus Folkesson }
341*b362de16SMarcus Folkesson 
342*b362de16SMarcus Folkesson static void st7571_primary_plane_helper_atomic_update(struct drm_plane *plane,
343*b362de16SMarcus Folkesson 						      struct drm_atomic_state *state)
344*b362de16SMarcus Folkesson {
345*b362de16SMarcus Folkesson 	struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane);
346*b362de16SMarcus Folkesson 	struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
347*b362de16SMarcus Folkesson 	struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(plane_state);
348*b362de16SMarcus Folkesson 	struct drm_framebuffer *fb = plane_state->fb;
349*b362de16SMarcus Folkesson 	struct drm_atomic_helper_damage_iter iter;
350*b362de16SMarcus Folkesson 	struct drm_device *drm = plane->dev;
351*b362de16SMarcus Folkesson 	struct drm_rect damage;
352*b362de16SMarcus Folkesson 	struct st7571_device *st7571 = drm_to_st7571(plane->dev);
353*b362de16SMarcus Folkesson 	int ret, idx;
354*b362de16SMarcus Folkesson 
355*b362de16SMarcus Folkesson 	if (!fb)
356*b362de16SMarcus Folkesson 		return; /* no framebuffer; plane is disabled */
357*b362de16SMarcus Folkesson 
358*b362de16SMarcus Folkesson 	ret = drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE);
359*b362de16SMarcus Folkesson 	if (ret)
360*b362de16SMarcus Folkesson 		return;
361*b362de16SMarcus Folkesson 
362*b362de16SMarcus Folkesson 	if (!drm_dev_enter(drm, &idx))
363*b362de16SMarcus Folkesson 		goto out_drm_gem_fb_end_cpu_access;
364*b362de16SMarcus Folkesson 
365*b362de16SMarcus Folkesson 	drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state);
366*b362de16SMarcus Folkesson 	drm_atomic_for_each_plane_damage(&iter, &damage) {
367*b362de16SMarcus Folkesson 		st7571->pformat->prepare_buffer(st7571,
368*b362de16SMarcus Folkesson 						&shadow_plane_state->data[0],
369*b362de16SMarcus Folkesson 						fb, &damage,
370*b362de16SMarcus Folkesson 						&shadow_plane_state->fmtcnv_state);
371*b362de16SMarcus Folkesson 
372*b362de16SMarcus Folkesson 		st7571->pformat->update_rect(fb, &damage);
373*b362de16SMarcus Folkesson 	}
374*b362de16SMarcus Folkesson 
375*b362de16SMarcus Folkesson 	drm_dev_exit(idx);
376*b362de16SMarcus Folkesson 
377*b362de16SMarcus Folkesson out_drm_gem_fb_end_cpu_access:
378*b362de16SMarcus Folkesson 	drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
379*b362de16SMarcus Folkesson }
380*b362de16SMarcus Folkesson 
381*b362de16SMarcus Folkesson static void st7571_primary_plane_helper_atomic_disable(struct drm_plane *plane,
382*b362de16SMarcus Folkesson 						       struct drm_atomic_state *state)
383*b362de16SMarcus Folkesson {
384*b362de16SMarcus Folkesson 	struct drm_device *drm = plane->dev;
385*b362de16SMarcus Folkesson 	struct st7571_device *st7571 = drm_to_st7571(plane->dev);
386*b362de16SMarcus Folkesson 	int idx;
387*b362de16SMarcus Folkesson 
388*b362de16SMarcus Folkesson 	if (!drm_dev_enter(drm, &idx))
389*b362de16SMarcus Folkesson 		return;
390*b362de16SMarcus Folkesson 
391*b362de16SMarcus Folkesson 	st7571_fb_clear_screen(st7571);
392*b362de16SMarcus Folkesson 	drm_dev_exit(idx);
393*b362de16SMarcus Folkesson }
394*b362de16SMarcus Folkesson 
395*b362de16SMarcus Folkesson static const struct drm_plane_helper_funcs st7571_primary_plane_helper_funcs = {
396*b362de16SMarcus Folkesson 	DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
397*b362de16SMarcus Folkesson 	.atomic_check = st7571_primary_plane_helper_atomic_check,
398*b362de16SMarcus Folkesson 	.atomic_update = st7571_primary_plane_helper_atomic_update,
399*b362de16SMarcus Folkesson 	.atomic_disable = st7571_primary_plane_helper_atomic_disable,
400*b362de16SMarcus Folkesson };
401*b362de16SMarcus Folkesson 
402*b362de16SMarcus Folkesson static const struct drm_plane_funcs st7571_primary_plane_funcs = {
403*b362de16SMarcus Folkesson 	.update_plane = drm_atomic_helper_update_plane,
404*b362de16SMarcus Folkesson 	.disable_plane = drm_atomic_helper_disable_plane,
405*b362de16SMarcus Folkesson 	.destroy = drm_plane_cleanup,
406*b362de16SMarcus Folkesson 	DRM_GEM_SHADOW_PLANE_FUNCS,
407*b362de16SMarcus Folkesson };
408*b362de16SMarcus Folkesson 
409*b362de16SMarcus Folkesson /*
410*b362de16SMarcus Folkesson  * CRTC
411*b362de16SMarcus Folkesson  */
412*b362de16SMarcus Folkesson 
413*b362de16SMarcus Folkesson static enum drm_mode_status st7571_crtc_mode_valid(struct drm_crtc *crtc,
414*b362de16SMarcus Folkesson 						   const struct drm_display_mode *mode)
415*b362de16SMarcus Folkesson {
416*b362de16SMarcus Folkesson 	struct st7571_device *st7571 = drm_to_st7571(crtc->dev);
417*b362de16SMarcus Folkesson 
418*b362de16SMarcus Folkesson 	return drm_crtc_helper_mode_valid_fixed(crtc, mode, &st7571->mode);
419*b362de16SMarcus Folkesson }
420*b362de16SMarcus Folkesson 
421*b362de16SMarcus Folkesson static const struct drm_crtc_helper_funcs st7571_crtc_helper_funcs = {
422*b362de16SMarcus Folkesson 	.atomic_check = drm_crtc_helper_atomic_check,
423*b362de16SMarcus Folkesson 	.mode_valid = st7571_crtc_mode_valid,
424*b362de16SMarcus Folkesson };
425*b362de16SMarcus Folkesson 
426*b362de16SMarcus Folkesson static const struct drm_crtc_funcs st7571_crtc_funcs = {
427*b362de16SMarcus Folkesson 	.reset = drm_atomic_helper_crtc_reset,
428*b362de16SMarcus Folkesson 	.destroy = drm_crtc_cleanup,
429*b362de16SMarcus Folkesson 	.set_config = drm_atomic_helper_set_config,
430*b362de16SMarcus Folkesson 	.page_flip = drm_atomic_helper_page_flip,
431*b362de16SMarcus Folkesson 	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
432*b362de16SMarcus Folkesson 	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
433*b362de16SMarcus Folkesson };
434*b362de16SMarcus Folkesson 
435*b362de16SMarcus Folkesson /*
436*b362de16SMarcus Folkesson  * Encoder
437*b362de16SMarcus Folkesson  */
438*b362de16SMarcus Folkesson 
439*b362de16SMarcus Folkesson static void st7571_encoder_atomic_enable(struct drm_encoder *encoder,
440*b362de16SMarcus Folkesson 					 struct drm_atomic_state *state)
441*b362de16SMarcus Folkesson {
442*b362de16SMarcus Folkesson 	struct drm_device *drm = encoder->dev;
443*b362de16SMarcus Folkesson 	struct st7571_device *st7571 = drm_to_st7571(drm);
444*b362de16SMarcus Folkesson 	u8 command = ST7571_DISPLAY_ON;
445*b362de16SMarcus Folkesson 	int ret;
446*b362de16SMarcus Folkesson 
447*b362de16SMarcus Folkesson 	ret = st7571->pdata->init(st7571);
448*b362de16SMarcus Folkesson 	if (ret)
449*b362de16SMarcus Folkesson 		return;
450*b362de16SMarcus Folkesson 
451*b362de16SMarcus Folkesson 	st7571_send_command_list(st7571, &command, 1);
452*b362de16SMarcus Folkesson }
453*b362de16SMarcus Folkesson 
454*b362de16SMarcus Folkesson static void st7571_encoder_atomic_disable(struct drm_encoder *encoder,
455*b362de16SMarcus Folkesson 					  struct drm_atomic_state *state)
456*b362de16SMarcus Folkesson {
457*b362de16SMarcus Folkesson 	struct drm_device *drm = encoder->dev;
458*b362de16SMarcus Folkesson 	struct st7571_device *st7571 = drm_to_st7571(drm);
459*b362de16SMarcus Folkesson 	u8 command = ST7571_DISPLAY_OFF;
460*b362de16SMarcus Folkesson 
461*b362de16SMarcus Folkesson 	st7571_send_command_list(st7571, &command, 1);
462*b362de16SMarcus Folkesson }
463*b362de16SMarcus Folkesson 
464*b362de16SMarcus Folkesson static const struct drm_encoder_funcs st7571_encoder_funcs = {
465*b362de16SMarcus Folkesson 	.destroy = drm_encoder_cleanup,
466*b362de16SMarcus Folkesson 
467*b362de16SMarcus Folkesson };
468*b362de16SMarcus Folkesson 
469*b362de16SMarcus Folkesson static const struct drm_encoder_helper_funcs st7571_encoder_helper_funcs = {
470*b362de16SMarcus Folkesson 	.atomic_enable = st7571_encoder_atomic_enable,
471*b362de16SMarcus Folkesson 	.atomic_disable = st7571_encoder_atomic_disable,
472*b362de16SMarcus Folkesson };
473*b362de16SMarcus Folkesson 
474*b362de16SMarcus Folkesson /*
475*b362de16SMarcus Folkesson  * Connector
476*b362de16SMarcus Folkesson  */
477*b362de16SMarcus Folkesson 
478*b362de16SMarcus Folkesson static const struct drm_connector_funcs st7571_connector_funcs = {
479*b362de16SMarcus Folkesson 	.reset = drm_atomic_helper_connector_reset,
480*b362de16SMarcus Folkesson 	.fill_modes = drm_helper_probe_single_connector_modes,
481*b362de16SMarcus Folkesson 	.destroy = drm_connector_cleanup,
482*b362de16SMarcus Folkesson 	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
483*b362de16SMarcus Folkesson 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
484*b362de16SMarcus Folkesson };
485*b362de16SMarcus Folkesson 
486*b362de16SMarcus Folkesson static const struct drm_mode_config_funcs st7571_mode_config_funcs = {
487*b362de16SMarcus Folkesson 	.fb_create = drm_gem_fb_create_with_dirty,
488*b362de16SMarcus Folkesson 	.atomic_check = drm_atomic_helper_check,
489*b362de16SMarcus Folkesson 	.atomic_commit = drm_atomic_helper_commit,
490*b362de16SMarcus Folkesson };
491*b362de16SMarcus Folkesson 
492*b362de16SMarcus Folkesson static struct drm_display_mode st7571_mode(struct st7571_device *st7571)
493*b362de16SMarcus Folkesson {
494*b362de16SMarcus Folkesson 	struct drm_display_mode mode = {
495*b362de16SMarcus Folkesson 		DRM_SIMPLE_MODE(st7571->ncols, st7571->nlines,
496*b362de16SMarcus Folkesson 				st7571->width_mm, st7571->height_mm),
497*b362de16SMarcus Folkesson 	};
498*b362de16SMarcus Folkesson 
499*b362de16SMarcus Folkesson 	return mode;
500*b362de16SMarcus Folkesson }
501*b362de16SMarcus Folkesson 
502*b362de16SMarcus Folkesson static int st7571_mode_config_init(struct st7571_device *st7571)
503*b362de16SMarcus Folkesson {
504*b362de16SMarcus Folkesson 	struct drm_device *drm = &st7571->drm;
505*b362de16SMarcus Folkesson 	const struct st7571_panel_constraints *constraints = &st7571->pdata->constraints;
506*b362de16SMarcus Folkesson 	int ret;
507*b362de16SMarcus Folkesson 
508*b362de16SMarcus Folkesson 	ret = drmm_mode_config_init(drm);
509*b362de16SMarcus Folkesson 	if (ret)
510*b362de16SMarcus Folkesson 		return ret;
511*b362de16SMarcus Folkesson 
512*b362de16SMarcus Folkesson 	drm->mode_config.min_width = constraints->min_ncols;
513*b362de16SMarcus Folkesson 	drm->mode_config.min_height = constraints->min_nlines;
514*b362de16SMarcus Folkesson 	drm->mode_config.max_width = constraints->max_ncols;
515*b362de16SMarcus Folkesson 	drm->mode_config.max_height = constraints->max_nlines;
516*b362de16SMarcus Folkesson 	drm->mode_config.preferred_depth = 24;
517*b362de16SMarcus Folkesson 	drm->mode_config.funcs = &st7571_mode_config_funcs;
518*b362de16SMarcus Folkesson 
519*b362de16SMarcus Folkesson 	return 0;
520*b362de16SMarcus Folkesson }
521*b362de16SMarcus Folkesson 
522*b362de16SMarcus Folkesson static int st7571_plane_init(struct st7571_device *st7571,
523*b362de16SMarcus Folkesson 			     const struct st7571_panel_format *pformat)
524*b362de16SMarcus Folkesson {
525*b362de16SMarcus Folkesson 	struct drm_plane *primary_plane = &st7571->primary_plane;
526*b362de16SMarcus Folkesson 	struct drm_device *drm = &st7571->drm;
527*b362de16SMarcus Folkesson 	int ret;
528*b362de16SMarcus Folkesson 
529*b362de16SMarcus Folkesson 	ret = drm_universal_plane_init(drm, primary_plane, 0,
530*b362de16SMarcus Folkesson 				       &st7571_primary_plane_funcs,
531*b362de16SMarcus Folkesson 				       pformat->formats,
532*b362de16SMarcus Folkesson 				       pformat->nformats,
533*b362de16SMarcus Folkesson 				       st7571_primary_plane_fmtmods,
534*b362de16SMarcus Folkesson 				       DRM_PLANE_TYPE_PRIMARY, NULL);
535*b362de16SMarcus Folkesson 	if (ret)
536*b362de16SMarcus Folkesson 		return ret;
537*b362de16SMarcus Folkesson 
538*b362de16SMarcus Folkesson 	drm_plane_helper_add(primary_plane, &st7571_primary_plane_helper_funcs);
539*b362de16SMarcus Folkesson 	drm_plane_enable_fb_damage_clips(primary_plane);
540*b362de16SMarcus Folkesson 
541*b362de16SMarcus Folkesson 	return 0;
542*b362de16SMarcus Folkesson }
543*b362de16SMarcus Folkesson 
544*b362de16SMarcus Folkesson static int st7571_crtc_init(struct st7571_device *st7571)
545*b362de16SMarcus Folkesson {
546*b362de16SMarcus Folkesson 	struct drm_plane *primary_plane = &st7571->primary_plane;
547*b362de16SMarcus Folkesson 	struct drm_crtc *crtc = &st7571->crtc;
548*b362de16SMarcus Folkesson 	struct drm_device *drm = &st7571->drm;
549*b362de16SMarcus Folkesson 	int ret;
550*b362de16SMarcus Folkesson 
551*b362de16SMarcus Folkesson 	ret = drm_crtc_init_with_planes(drm, crtc, primary_plane, NULL,
552*b362de16SMarcus Folkesson 					&st7571_crtc_funcs, NULL);
553*b362de16SMarcus Folkesson 	if (ret)
554*b362de16SMarcus Folkesson 		return ret;
555*b362de16SMarcus Folkesson 
556*b362de16SMarcus Folkesson 	drm_crtc_helper_add(crtc, &st7571_crtc_helper_funcs);
557*b362de16SMarcus Folkesson 
558*b362de16SMarcus Folkesson 	return 0;
559*b362de16SMarcus Folkesson }
560*b362de16SMarcus Folkesson 
561*b362de16SMarcus Folkesson static int st7571_encoder_init(struct st7571_device *st7571)
562*b362de16SMarcus Folkesson {
563*b362de16SMarcus Folkesson 	struct drm_encoder *encoder = &st7571->encoder;
564*b362de16SMarcus Folkesson 	struct drm_crtc *crtc = &st7571->crtc;
565*b362de16SMarcus Folkesson 	struct drm_device *drm = &st7571->drm;
566*b362de16SMarcus Folkesson 	int ret;
567*b362de16SMarcus Folkesson 
568*b362de16SMarcus Folkesson 	ret = drm_encoder_init(drm, encoder, &st7571_encoder_funcs, DRM_MODE_ENCODER_NONE, NULL);
569*b362de16SMarcus Folkesson 	if (ret)
570*b362de16SMarcus Folkesson 		return ret;
571*b362de16SMarcus Folkesson 
572*b362de16SMarcus Folkesson 	drm_encoder_helper_add(encoder, &st7571_encoder_helper_funcs);
573*b362de16SMarcus Folkesson 
574*b362de16SMarcus Folkesson 	encoder->possible_crtcs = drm_crtc_mask(crtc);
575*b362de16SMarcus Folkesson 
576*b362de16SMarcus Folkesson 	return 0;
577*b362de16SMarcus Folkesson }
578*b362de16SMarcus Folkesson 
579*b362de16SMarcus Folkesson static int st7571_connector_init(struct st7571_device *st7571)
580*b362de16SMarcus Folkesson {
581*b362de16SMarcus Folkesson 	struct drm_connector *connector = &st7571->connector;
582*b362de16SMarcus Folkesson 	struct drm_encoder *encoder = &st7571->encoder;
583*b362de16SMarcus Folkesson 	struct drm_device *drm = &st7571->drm;
584*b362de16SMarcus Folkesson 	int ret;
585*b362de16SMarcus Folkesson 
586*b362de16SMarcus Folkesson 	ret = drm_connector_init(drm, connector, &st7571_connector_funcs,
587*b362de16SMarcus Folkesson 				 DRM_MODE_CONNECTOR_Unknown);
588*b362de16SMarcus Folkesson 	if (ret)
589*b362de16SMarcus Folkesson 		return ret;
590*b362de16SMarcus Folkesson 
591*b362de16SMarcus Folkesson 	drm_connector_helper_add(connector, &st7571_connector_helper_funcs);
592*b362de16SMarcus Folkesson 
593*b362de16SMarcus Folkesson 	return drm_connector_attach_encoder(connector, encoder);
594*b362de16SMarcus Folkesson }
595*b362de16SMarcus Folkesson 
596*b362de16SMarcus Folkesson DEFINE_DRM_GEM_FOPS(st7571_fops);
597*b362de16SMarcus Folkesson 
598*b362de16SMarcus Folkesson static const struct drm_driver st7571_driver = {
599*b362de16SMarcus Folkesson 	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
600*b362de16SMarcus Folkesson 
601*b362de16SMarcus Folkesson 	.name		 = DRIVER_NAME,
602*b362de16SMarcus Folkesson 	.desc		 = DRIVER_DESC,
603*b362de16SMarcus Folkesson 	.major		 = DRIVER_MAJOR,
604*b362de16SMarcus Folkesson 	.minor		 = DRIVER_MINOR,
605*b362de16SMarcus Folkesson 
606*b362de16SMarcus Folkesson 	.fops		 = &st7571_fops,
607*b362de16SMarcus Folkesson 	DRM_GEM_SHMEM_DRIVER_OPS,
608*b362de16SMarcus Folkesson 	DRM_FBDEV_SHMEM_DRIVER_OPS,
609*b362de16SMarcus Folkesson };
610*b362de16SMarcus Folkesson 
611*b362de16SMarcus Folkesson static int st7571_validate_parameters(struct st7571_device *st7571)
612*b362de16SMarcus Folkesson {
613*b362de16SMarcus Folkesson 	struct device *dev = st7571->dev;
614*b362de16SMarcus Folkesson 	const struct st7571_panel_constraints *constraints = &st7571->pdata->constraints;
615*b362de16SMarcus Folkesson 
616*b362de16SMarcus Folkesson 	if (st7571->width_mm  == 0) {
617*b362de16SMarcus Folkesson 		dev_err(dev, "Invalid panel width\n");
618*b362de16SMarcus Folkesson 		return -EINVAL;
619*b362de16SMarcus Folkesson 	}
620*b362de16SMarcus Folkesson 
621*b362de16SMarcus Folkesson 	if (st7571->height_mm == 0) {
622*b362de16SMarcus Folkesson 		dev_err(dev, "Invalid panel height\n");
623*b362de16SMarcus Folkesson 		return -EINVAL;
624*b362de16SMarcus Folkesson 	}
625*b362de16SMarcus Folkesson 
626*b362de16SMarcus Folkesson 	if (st7571->nlines < constraints->min_nlines ||
627*b362de16SMarcus Folkesson 	    st7571->nlines > constraints->max_nlines) {
628*b362de16SMarcus Folkesson 		dev_err(dev, "Invalid timing configuration.\n");
629*b362de16SMarcus Folkesson 		return -EINVAL;
630*b362de16SMarcus Folkesson 	}
631*b362de16SMarcus Folkesson 
632*b362de16SMarcus Folkesson 	if (st7571->startline + st7571->nlines > constraints->max_nlines) {
633*b362de16SMarcus Folkesson 		dev_err(dev, "Invalid timing configuration.\n");
634*b362de16SMarcus Folkesson 		return -EINVAL;
635*b362de16SMarcus Folkesson 	}
636*b362de16SMarcus Folkesson 
637*b362de16SMarcus Folkesson 	if (st7571->ncols < constraints->min_ncols ||
638*b362de16SMarcus Folkesson 	    st7571->ncols > constraints->max_ncols) {
639*b362de16SMarcus Folkesson 		dev_err(dev, "Invalid timing configuration.\n");
640*b362de16SMarcus Folkesson 		return -EINVAL;
641*b362de16SMarcus Folkesson 	}
642*b362de16SMarcus Folkesson 
643*b362de16SMarcus Folkesson 	if (st7571->grayscale && !constraints->support_grayscale) {
644*b362de16SMarcus Folkesson 		dev_err(dev, "Grayscale not supported\n");
645*b362de16SMarcus Folkesson 		return -EINVAL;
646*b362de16SMarcus Folkesson 	}
647*b362de16SMarcus Folkesson 
648*b362de16SMarcus Folkesson 	return 0;
649*b362de16SMarcus Folkesson }
650*b362de16SMarcus Folkesson 
651*b362de16SMarcus Folkesson static int st7567_parse_dt(struct st7571_device *st7567)
652*b362de16SMarcus Folkesson {
653*b362de16SMarcus Folkesson 	struct device *dev = st7567->dev;
654*b362de16SMarcus Folkesson 	struct device_node *np = dev->of_node;
655*b362de16SMarcus Folkesson 	struct display_timing dt;
656*b362de16SMarcus Folkesson 	int ret;
657*b362de16SMarcus Folkesson 
658*b362de16SMarcus Folkesson 	ret = of_get_display_timing(np, "panel-timing", &dt);
659*b362de16SMarcus Folkesson 	if (ret) {
660*b362de16SMarcus Folkesson 		dev_err(dev, "Failed to get display timing from DT\n");
661*b362de16SMarcus Folkesson 		return ret;
662*b362de16SMarcus Folkesson 	}
663*b362de16SMarcus Folkesson 
664*b362de16SMarcus Folkesson 	of_property_read_u32(np, "width-mm", &st7567->width_mm);
665*b362de16SMarcus Folkesson 	of_property_read_u32(np, "height-mm", &st7567->height_mm);
666*b362de16SMarcus Folkesson 	st7567->inverted = of_property_read_bool(np, "sitronix,inverted");
667*b362de16SMarcus Folkesson 
668*b362de16SMarcus Folkesson 	st7567->pformat = &st7571_monochrome;
669*b362de16SMarcus Folkesson 	st7567->bpp = 1;
670*b362de16SMarcus Folkesson 
671*b362de16SMarcus Folkesson 	st7567->startline = dt.vfront_porch.typ;
672*b362de16SMarcus Folkesson 	st7567->nlines = dt.vactive.typ;
673*b362de16SMarcus Folkesson 	st7567->ncols = dt.hactive.typ;
674*b362de16SMarcus Folkesson 
675*b362de16SMarcus Folkesson 	return 0;
676*b362de16SMarcus Folkesson }
677*b362de16SMarcus Folkesson 
678*b362de16SMarcus Folkesson static int st7571_parse_dt(struct st7571_device *st7571)
679*b362de16SMarcus Folkesson {
680*b362de16SMarcus Folkesson 	struct device *dev = st7571->dev;
681*b362de16SMarcus Folkesson 	struct device_node *np = dev->of_node;
682*b362de16SMarcus Folkesson 	struct display_timing dt;
683*b362de16SMarcus Folkesson 	int ret;
684*b362de16SMarcus Folkesson 
685*b362de16SMarcus Folkesson 	ret = of_get_display_timing(np, "panel-timing", &dt);
686*b362de16SMarcus Folkesson 	if (ret) {
687*b362de16SMarcus Folkesson 		dev_err(dev, "Failed to get display timing from DT\n");
688*b362de16SMarcus Folkesson 		return ret;
689*b362de16SMarcus Folkesson 	}
690*b362de16SMarcus Folkesson 
691*b362de16SMarcus Folkesson 	of_property_read_u32(np, "width-mm", &st7571->width_mm);
692*b362de16SMarcus Folkesson 	of_property_read_u32(np, "height-mm", &st7571->height_mm);
693*b362de16SMarcus Folkesson 	st7571->grayscale = of_property_read_bool(np, "sitronix,grayscale");
694*b362de16SMarcus Folkesson 	st7571->inverted = of_property_read_bool(np, "sitronix,inverted");
695*b362de16SMarcus Folkesson 
696*b362de16SMarcus Folkesson 	if (st7571->grayscale) {
697*b362de16SMarcus Folkesson 		st7571->pformat = &st7571_grayscale;
698*b362de16SMarcus Folkesson 		st7571->bpp = 2;
699*b362de16SMarcus Folkesson 	} else {
700*b362de16SMarcus Folkesson 		st7571->pformat = &st7571_monochrome;
701*b362de16SMarcus Folkesson 		st7571->bpp = 1;
702*b362de16SMarcus Folkesson 	}
703*b362de16SMarcus Folkesson 
704*b362de16SMarcus Folkesson 	st7571->startline = dt.vfront_porch.typ;
705*b362de16SMarcus Folkesson 	st7571->nlines = dt.vactive.typ;
706*b362de16SMarcus Folkesson 	st7571->ncols = dt.hactive.typ;
707*b362de16SMarcus Folkesson 
708*b362de16SMarcus Folkesson 	st7571->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
709*b362de16SMarcus Folkesson 	if (IS_ERR(st7571->reset))
710*b362de16SMarcus Folkesson 		return dev_err_probe(dev, PTR_ERR(st7571->reset),
711*b362de16SMarcus Folkesson 				     "Failed to get reset gpio\n");
712*b362de16SMarcus Folkesson 
713*b362de16SMarcus Folkesson 	return 0;
714*b362de16SMarcus Folkesson }
715*b362de16SMarcus Folkesson 
716*b362de16SMarcus Folkesson static void st7571_reset(struct st7571_device *st7571)
717*b362de16SMarcus Folkesson {
718*b362de16SMarcus Folkesson 	gpiod_set_value_cansleep(st7571->reset, 1);
719*b362de16SMarcus Folkesson 	fsleep(20);
720*b362de16SMarcus Folkesson 	gpiod_set_value_cansleep(st7571->reset, 0);
721*b362de16SMarcus Folkesson }
722*b362de16SMarcus Folkesson 
723*b362de16SMarcus Folkesson static int st7567_lcd_init(struct st7571_device *st7567)
724*b362de16SMarcus Folkesson {
725*b362de16SMarcus Folkesson 	/*
726*b362de16SMarcus Folkesson 	 * Most of the initialization sequence is taken directly from the
727*b362de16SMarcus Folkesson 	 * referential initial code in the ST7567 datasheet.
728*b362de16SMarcus Folkesson 	 */
729*b362de16SMarcus Folkesson 	u8 commands[] = {
730*b362de16SMarcus Folkesson 		ST7571_DISPLAY_OFF,
731*b362de16SMarcus Folkesson 
732*b362de16SMarcus Folkesson 		ST7567_SET_LCD_BIAS(1),
733*b362de16SMarcus Folkesson 
734*b362de16SMarcus Folkesson 		ST7571_SET_SEG_SCAN_DIR(0),
735*b362de16SMarcus Folkesson 		ST7571_SET_COM_SCAN_DIR(1),
736*b362de16SMarcus Folkesson 
737*b362de16SMarcus Folkesson 		ST7571_SET_REGULATOR_REG(4),
738*b362de16SMarcus Folkesson 		ST7571_SET_CONTRAST_MSB,
739*b362de16SMarcus Folkesson 		ST7571_SET_CONTRAST_LSB(0x20),
740*b362de16SMarcus Folkesson 
741*b362de16SMarcus Folkesson 		ST7571_SET_START_LINE_MSB,
742*b362de16SMarcus Folkesson 		ST7571_SET_START_LINE_LSB(st7567->startline),
743*b362de16SMarcus Folkesson 
744*b362de16SMarcus Folkesson 		ST7571_SET_POWER(0x4),	/* Power Control, VC: ON, VR: OFF, VF: OFF */
745*b362de16SMarcus Folkesson 		ST7571_SET_POWER(0x6),	/* Power Control, VC: ON, VR: ON, VF: OFF */
746*b362de16SMarcus Folkesson 		ST7571_SET_POWER(0x7),	/* Power Control, VC: ON, VR: ON, VF: ON */
747*b362de16SMarcus Folkesson 
748*b362de16SMarcus Folkesson 		ST7571_SET_REVERSE(st7567->inverted ? 1 : 0),
749*b362de16SMarcus Folkesson 		ST7571_SET_ENTIRE_DISPLAY_ON(0),
750*b362de16SMarcus Folkesson 	};
751*b362de16SMarcus Folkesson 
752*b362de16SMarcus Folkesson 	return st7571_send_command_list(st7567, commands, ARRAY_SIZE(commands));
753*b362de16SMarcus Folkesson }
754*b362de16SMarcus Folkesson 
755*b362de16SMarcus Folkesson static int st7571_lcd_init(struct st7571_device *st7571)
756*b362de16SMarcus Folkesson {
757*b362de16SMarcus Folkesson 	/*
758*b362de16SMarcus Folkesson 	 * Most of the initialization sequence is taken directly from the
759*b362de16SMarcus Folkesson 	 * referential initial code in the ST7571 datasheet.
760*b362de16SMarcus Folkesson 	 */
761*b362de16SMarcus Folkesson 	u8 commands[] = {
762*b362de16SMarcus Folkesson 		ST7571_DISPLAY_OFF,
763*b362de16SMarcus Folkesson 
764*b362de16SMarcus Folkesson 		ST7571_SET_MODE_MSB,
765*b362de16SMarcus Folkesson 		ST7571_SET_MODE_LSB(0x2e),
766*b362de16SMarcus Folkesson 
767*b362de16SMarcus Folkesson 		ST7571_SET_SEG_SCAN_DIR(0),
768*b362de16SMarcus Folkesson 		ST7571_SET_COM_SCAN_DIR(1),
769*b362de16SMarcus Folkesson 
770*b362de16SMarcus Folkesson 		ST7571_SET_COM0_MSB,
771*b362de16SMarcus Folkesson 		ST7571_SET_COM0_LSB(0x00),
772*b362de16SMarcus Folkesson 
773*b362de16SMarcus Folkesson 		ST7571_SET_START_LINE_MSB,
774*b362de16SMarcus Folkesson 		ST7571_SET_START_LINE_LSB(st7571->startline),
775*b362de16SMarcus Folkesson 
776*b362de16SMarcus Folkesson 		ST7571_OSC_ON,
777*b362de16SMarcus Folkesson 		ST7571_SET_REGULATOR_REG(5),
778*b362de16SMarcus Folkesson 		ST7571_SET_CONTRAST_MSB,
779*b362de16SMarcus Folkesson 		ST7571_SET_CONTRAST_LSB(0x33),
780*b362de16SMarcus Folkesson 		ST7571_SET_LCD_BIAS(0x04),
781*b362de16SMarcus Folkesson 		ST7571_SET_DISPLAY_DUTY_MSB,
782*b362de16SMarcus Folkesson 		ST7571_SET_DISPLAY_DUTY_LSB(st7571->nlines),
783*b362de16SMarcus Folkesson 
784*b362de16SMarcus Folkesson 		ST7571_SET_POWER(0x4),	/* Power Control, VC: ON, VR: OFF, VF: OFF */
785*b362de16SMarcus Folkesson 		ST7571_SET_POWER(0x6),	/* Power Control, VC: ON, VR: ON, VF: OFF */
786*b362de16SMarcus Folkesson 		ST7571_SET_POWER(0x7),	/* Power Control, VC: ON, VR: ON, VF: ON */
787*b362de16SMarcus Folkesson 
788*b362de16SMarcus Folkesson 		ST7571_COMMAND_SET_3,
789*b362de16SMarcus Folkesson 		ST7571_SET_COLOR_MODE(st7571->pformat->mode),
790*b362de16SMarcus Folkesson 		ST7571_COMMAND_SET_NORMAL,
791*b362de16SMarcus Folkesson 
792*b362de16SMarcus Folkesson 		ST7571_SET_REVERSE(st7571->inverted ? 1 : 0),
793*b362de16SMarcus Folkesson 		ST7571_SET_ENTIRE_DISPLAY_ON(0),
794*b362de16SMarcus Folkesson 	};
795*b362de16SMarcus Folkesson 
796*b362de16SMarcus Folkesson 	/* Perform a reset before initializing the controller */
797*b362de16SMarcus Folkesson 	st7571_reset(st7571);
798*b362de16SMarcus Folkesson 
799*b362de16SMarcus Folkesson 	return st7571_send_command_list(st7571, commands, ARRAY_SIZE(commands));
800*b362de16SMarcus Folkesson }
801*b362de16SMarcus Folkesson 
802*b362de16SMarcus Folkesson struct st7571_device *st7571_probe(struct device *dev,
803*b362de16SMarcus Folkesson 				   struct regmap *regmap)
804*b362de16SMarcus Folkesson {
805*b362de16SMarcus Folkesson 	struct st7571_device *st7571;
806*b362de16SMarcus Folkesson 	struct drm_device *drm;
807*b362de16SMarcus Folkesson 	int ret;
808*b362de16SMarcus Folkesson 
809*b362de16SMarcus Folkesson 	st7571 = devm_drm_dev_alloc(dev, &st7571_driver,
810*b362de16SMarcus Folkesson 				    struct st7571_device, drm);
811*b362de16SMarcus Folkesson 	if (IS_ERR(st7571))
812*b362de16SMarcus Folkesson 		return st7571;
813*b362de16SMarcus Folkesson 
814*b362de16SMarcus Folkesson 	drm = &st7571->drm;
815*b362de16SMarcus Folkesson 	st7571->dev = dev;
816*b362de16SMarcus Folkesson 	st7571->pdata = device_get_match_data(st7571->dev);
817*b362de16SMarcus Folkesson 
818*b362de16SMarcus Folkesson 	ret = st7571->pdata->parse_dt(st7571);
819*b362de16SMarcus Folkesson 	if (ret)
820*b362de16SMarcus Folkesson 		return ERR_PTR(ret);
821*b362de16SMarcus Folkesson 
822*b362de16SMarcus Folkesson 	ret = st7571_validate_parameters(st7571);
823*b362de16SMarcus Folkesson 	if (ret)
824*b362de16SMarcus Folkesson 		return ERR_PTR(ret);
825*b362de16SMarcus Folkesson 
826*b362de16SMarcus Folkesson 	st7571->mode = st7571_mode(st7571);
827*b362de16SMarcus Folkesson 	st7571->regmap = regmap;
828*b362de16SMarcus Folkesson 
829*b362de16SMarcus Folkesson 	st7571->hwbuf = devm_kzalloc(st7571->dev,
830*b362de16SMarcus Folkesson 				     (st7571->nlines * st7571->ncols * st7571->bpp) / 8,
831*b362de16SMarcus Folkesson 				     GFP_KERNEL);
832*b362de16SMarcus Folkesson 	if (!st7571->hwbuf)
833*b362de16SMarcus Folkesson 		return ERR_PTR(-ENOMEM);
834*b362de16SMarcus Folkesson 
835*b362de16SMarcus Folkesson 	st7571->row = devm_kzalloc(st7571->dev,
836*b362de16SMarcus Folkesson 				   (st7571->ncols * st7571->bpp),
837*b362de16SMarcus Folkesson 				   GFP_KERNEL);
838*b362de16SMarcus Folkesson 	if (!st7571->row)
839*b362de16SMarcus Folkesson 		return ERR_PTR(-ENOMEM);
840*b362de16SMarcus Folkesson 
841*b362de16SMarcus Folkesson 	ret = st7571_mode_config_init(st7571);
842*b362de16SMarcus Folkesson 	if (ret) {
843*b362de16SMarcus Folkesson 		dev_err(st7571->dev, "Failed to initialize mode config\n");
844*b362de16SMarcus Folkesson 		return ERR_PTR(ret);
845*b362de16SMarcus Folkesson 	}
846*b362de16SMarcus Folkesson 
847*b362de16SMarcus Folkesson 	ret = st7571_plane_init(st7571, st7571->pformat);
848*b362de16SMarcus Folkesson 	if (ret) {
849*b362de16SMarcus Folkesson 		dev_err(st7571->dev, "Failed to initialize primary plane\n");
850*b362de16SMarcus Folkesson 		return ERR_PTR(ret);
851*b362de16SMarcus Folkesson 	}
852*b362de16SMarcus Folkesson 
853*b362de16SMarcus Folkesson 	ret = st7571_crtc_init(st7571);
854*b362de16SMarcus Folkesson 	if (ret < 0) {
855*b362de16SMarcus Folkesson 		dev_err(st7571->dev, "Failed to initialize CRTC\n");
856*b362de16SMarcus Folkesson 		return ERR_PTR(ret);
857*b362de16SMarcus Folkesson 	}
858*b362de16SMarcus Folkesson 
859*b362de16SMarcus Folkesson 	ret = st7571_encoder_init(st7571);
860*b362de16SMarcus Folkesson 	if (ret < 0) {
861*b362de16SMarcus Folkesson 		dev_err(st7571->dev, "Failed to initialize encoder\n");
862*b362de16SMarcus Folkesson 		return ERR_PTR(ret);
863*b362de16SMarcus Folkesson 	}
864*b362de16SMarcus Folkesson 
865*b362de16SMarcus Folkesson 	ret = st7571_connector_init(st7571);
866*b362de16SMarcus Folkesson 	if (ret < 0) {
867*b362de16SMarcus Folkesson 		dev_err(st7571->dev, "Failed to initialize connector\n");
868*b362de16SMarcus Folkesson 		return ERR_PTR(ret);
869*b362de16SMarcus Folkesson 	}
870*b362de16SMarcus Folkesson 
871*b362de16SMarcus Folkesson 	drm_mode_config_reset(drm);
872*b362de16SMarcus Folkesson 
873*b362de16SMarcus Folkesson 	ret = drm_dev_register(drm, 0);
874*b362de16SMarcus Folkesson 	if (ret) {
875*b362de16SMarcus Folkesson 		dev_err(st7571->dev, "Failed to register DRM device\n");
876*b362de16SMarcus Folkesson 		return ERR_PTR(ret);
877*b362de16SMarcus Folkesson 	}
878*b362de16SMarcus Folkesson 
879*b362de16SMarcus Folkesson 	drm_client_setup(drm, NULL);
880*b362de16SMarcus Folkesson 	return st7571;
881*b362de16SMarcus Folkesson }
882*b362de16SMarcus Folkesson EXPORT_SYMBOL_GPL(st7571_probe);
883*b362de16SMarcus Folkesson 
884*b362de16SMarcus Folkesson void st7571_remove(struct st7571_device *st7571)
885*b362de16SMarcus Folkesson {
886*b362de16SMarcus Folkesson 	drm_dev_unplug(&st7571->drm);
887*b362de16SMarcus Folkesson }
888*b362de16SMarcus Folkesson EXPORT_SYMBOL_GPL(st7571_remove);
889*b362de16SMarcus Folkesson 
890*b362de16SMarcus Folkesson const struct st7571_panel_data st7567_config = {
891*b362de16SMarcus Folkesson 	.init = st7567_lcd_init,
892*b362de16SMarcus Folkesson 	.parse_dt = st7567_parse_dt,
893*b362de16SMarcus Folkesson 	.constraints = {
894*b362de16SMarcus Folkesson 		.min_nlines = 1,
895*b362de16SMarcus Folkesson 		.max_nlines = 64,
896*b362de16SMarcus Folkesson 		.min_ncols = 128,
897*b362de16SMarcus Folkesson 		.max_ncols = 128,
898*b362de16SMarcus Folkesson 		.support_grayscale = false,
899*b362de16SMarcus Folkesson 	},
900*b362de16SMarcus Folkesson };
901*b362de16SMarcus Folkesson EXPORT_SYMBOL_NS_GPL(st7567_config, "DRM_ST7571");
902*b362de16SMarcus Folkesson 
903*b362de16SMarcus Folkesson const struct st7571_panel_data st7571_config = {
904*b362de16SMarcus Folkesson 	.init = st7571_lcd_init,
905*b362de16SMarcus Folkesson 	.parse_dt = st7571_parse_dt,
906*b362de16SMarcus Folkesson 	.constraints = {
907*b362de16SMarcus Folkesson 		.min_nlines = 1,
908*b362de16SMarcus Folkesson 		.max_nlines = 128,
909*b362de16SMarcus Folkesson 		.min_ncols = 128,
910*b362de16SMarcus Folkesson 		.max_ncols = 128,
911*b362de16SMarcus Folkesson 		.support_grayscale = true,
912*b362de16SMarcus Folkesson 	},
913*b362de16SMarcus Folkesson };
914*b362de16SMarcus Folkesson EXPORT_SYMBOL_NS_GPL(st7571_config, "DRM_ST7571");
915*b362de16SMarcus Folkesson 
916*b362de16SMarcus Folkesson MODULE_AUTHOR("Marcus Folkesson <marcus.folkesson@gmail.com>");
917*b362de16SMarcus Folkesson MODULE_DESCRIPTION("DRM Driver for Sitronix ST7571 LCD controller");
918*b362de16SMarcus Folkesson MODULE_LICENSE("GPL");
919