1 /*
2  * Copyright (c) 2020 Rohit Gujarathi
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #define DT_DRV_COMPAT   sharp_ls0xx
8 
9 #include <zephyr/logging/log.h>
10 LOG_MODULE_REGISTER(ls0xx, CONFIG_DISPLAY_LOG_LEVEL);
11 
12 #include <string.h>
13 #include <zephyr/device.h>
14 #include <zephyr/drivers/display.h>
15 #include <zephyr/init.h>
16 #include <zephyr/drivers/gpio.h>
17 #include <zephyr/drivers/spi.h>
18 #include <zephyr/sys/byteorder.h>
19 
20 /* Supports LS012B7DD01, LS012B7DD06, LS013B7DH03, LS013B7DH05
21  * LS013B7DH06, LS027B7DH01A, LS032B7DD02, LS044Q7DH01
22  */
23 
24 /* Note:
25  * -> high/1 means white, low/0 means black
26  * -> Display expects LSB first
27  */
28 
29 #define LS0XX_PANEL_WIDTH   DT_INST_PROP(0, width)
30 #define LS0XX_PANEL_HEIGHT  DT_INST_PROP(0, height)
31 
32 #define LS0XX_PIXELS_PER_BYTE  8U
33 /* Adding 2 for the line number and dummy byte
34  * line_buf format for each row.
35  * +-------------------+-------------------+----------------+
36  * | line num (8 bits) | data (WIDTH bits) | dummy (8 bits) |
37  * +-------------------+-------------------+----------------+
38  */
39 #define LS0XX_BYTES_PER_LINE  ((LS0XX_PANEL_WIDTH / LS0XX_PIXELS_PER_BYTE) + 2)
40 
41 #define LS0XX_BIT_WRITECMD    0x01
42 #define LS0XX_BIT_VCOM        0x02
43 #define LS0XX_BIT_CLEAR       0x04
44 
45 struct ls0xx_config {
46 	struct spi_dt_spec bus;
47 #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios)
48 	struct gpio_dt_spec disp_en_gpio;
49 #endif
50 #if DT_INST_NODE_HAS_PROP(0, extcomin_gpios)
51 	struct gpio_dt_spec extcomin_gpio;
52 #endif
53 };
54 
55 #if DT_INST_NODE_HAS_PROP(0, extcomin_gpios)
56 /* Driver will handle VCOM toggling */
ls0xx_vcom_toggle(void * a,void * b,void * c)57 static void ls0xx_vcom_toggle(void *a, void *b, void *c)
58 {
59 	const struct ls0xx_config *config = a;
60 
61 	while (1) {
62 		gpio_pin_toggle_dt(&config->extcomin_gpio);
63 		k_usleep(3);
64 		gpio_pin_toggle_dt(&config->extcomin_gpio);
65 		k_msleep(1000 / DT_INST_PROP(0, extcomin_frequency));
66 	}
67 }
68 
69 K_THREAD_STACK_DEFINE(vcom_toggle_stack, 256);
70 struct k_thread vcom_toggle_thread;
71 #endif
72 
ls0xx_blanking_off(const struct device * dev)73 static int ls0xx_blanking_off(const struct device *dev)
74 {
75 #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios)
76 	const struct ls0xx_config *config = dev->config;
77 
78 	return gpio_pin_set_dt(&config->disp_en_gpio, 1);
79 #else
80 	LOG_WRN("Unsupported");
81 	return -ENOTSUP;
82 #endif
83 }
84 
ls0xx_blanking_on(const struct device * dev)85 static int ls0xx_blanking_on(const struct device *dev)
86 {
87 #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios)
88 	const struct ls0xx_config *config = dev->config;
89 
90 	return gpio_pin_set_dt(&config->disp_en_gpio, 0);
91 #else
92 	LOG_WRN("Unsupported");
93 	return -ENOTSUP;
94 #endif
95 }
96 
ls0xx_cmd(const struct device * dev,uint8_t * buf,uint8_t len)97 static int ls0xx_cmd(const struct device *dev, uint8_t *buf, uint8_t len)
98 {
99 	const struct ls0xx_config *config = dev->config;
100 	struct spi_buf cmd_buf = { .buf = buf, .len = len };
101 	struct spi_buf_set buf_set = { .buffers = &cmd_buf, .count = 1 };
102 
103 	return spi_write_dt(&config->bus, &buf_set);
104 }
105 
ls0xx_clear(const struct device * dev)106 static int ls0xx_clear(const struct device *dev)
107 {
108 	const struct ls0xx_config *config = dev->config;
109 	uint8_t clear_cmd[2] = { LS0XX_BIT_CLEAR, 0 };
110 	int err;
111 
112 	err = ls0xx_cmd(dev, clear_cmd, sizeof(clear_cmd));
113 	spi_release_dt(&config->bus);
114 
115 	return err;
116 }
117 
ls0xx_update_display(const struct device * dev,uint16_t start_line,uint16_t num_lines,const uint8_t * data)118 static int ls0xx_update_display(const struct device *dev,
119 				uint16_t start_line,
120 				uint16_t num_lines,
121 				const uint8_t *data)
122 {
123 	const struct ls0xx_config *config = dev->config;
124 	uint8_t write_cmd[1] = { LS0XX_BIT_WRITECMD };
125 	uint8_t ln = start_line;
126 	uint8_t dummy = 27;
127 	struct spi_buf line_buf[3] = {
128 		{
129 			.len = sizeof(ln),
130 			.buf = &ln,
131 		},
132 		{
133 			.len = LS0XX_BYTES_PER_LINE - 2,
134 		},
135 		{
136 			.len = sizeof(dummy),
137 			.buf = &dummy,
138 		},
139 	};
140 	struct spi_buf_set line_set = {
141 		.buffers = line_buf,
142 		.count = ARRAY_SIZE(line_buf),
143 	};
144 	int err;
145 
146 	LOG_DBG("Lines %d to %d", start_line, start_line + num_lines - 1);
147 	err = ls0xx_cmd(dev, write_cmd, sizeof(write_cmd));
148 
149 	/* Send each line to the screen including
150 	 * the line number and dummy bits
151 	 */
152 	for (; ln <= start_line + num_lines - 1; ln++) {
153 		line_buf[1].buf = (uint8_t *)data;
154 		err |= spi_write_dt(&config->bus, &line_set);
155 		data += LS0XX_PANEL_WIDTH / LS0XX_PIXELS_PER_BYTE;
156 	}
157 
158 	/* Send another trailing 8 bits for the last line
159 	 * These can be any bits, it does not matter
160 	 * just reusing the write_cmd buffer
161 	 */
162 	err |= ls0xx_cmd(dev, write_cmd, sizeof(write_cmd));
163 
164 	spi_release_dt(&config->bus);
165 
166 	return err;
167 }
168 
169 /* Buffer width should be equal to display width */
ls0xx_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)170 static int ls0xx_write(const struct device *dev, const uint16_t x,
171 		       const uint16_t y,
172 		       const struct display_buffer_descriptor *desc,
173 		       const void *buf)
174 {
175 	LOG_DBG("X: %d, Y: %d, W: %d, H: %d", x, y, desc->width, desc->height);
176 
177 	if (buf == NULL) {
178 		LOG_WRN("Display buffer is not available");
179 		return -EINVAL;
180 	}
181 
182 	if (desc->width != LS0XX_PANEL_WIDTH) {
183 		LOG_ERR("Width not a multiple of %d", LS0XX_PANEL_WIDTH);
184 		return -EINVAL;
185 	}
186 
187 	if (desc->pitch != desc->width) {
188 		LOG_ERR("Unsupported mode");
189 		return -ENOTSUP;
190 	}
191 
192 	if ((y + desc->height) > LS0XX_PANEL_HEIGHT) {
193 		LOG_ERR("Buffer out of bounds (height)");
194 		return -EINVAL;
195 	}
196 
197 	if (x != 0) {
198 		LOG_ERR("X-coordinate has to be 0");
199 		return -EINVAL;
200 	}
201 
202 	/* Adding 1 since line numbering on the display starts with 1 */
203 	return ls0xx_update_display(dev, y + 1, desc->height, buf);
204 }
205 
ls0xx_get_capabilities(const struct device * dev,struct display_capabilities * caps)206 static void ls0xx_get_capabilities(const struct device *dev,
207 				   struct display_capabilities *caps)
208 {
209 	memset(caps, 0, sizeof(struct display_capabilities));
210 	caps->x_resolution = LS0XX_PANEL_WIDTH;
211 	caps->y_resolution = LS0XX_PANEL_HEIGHT;
212 	caps->supported_pixel_formats = PIXEL_FORMAT_MONO01;
213 	caps->current_pixel_format = PIXEL_FORMAT_MONO01;
214 	caps->screen_info = SCREEN_INFO_X_ALIGNMENT_WIDTH;
215 }
216 
ls0xx_set_pixel_format(const struct device * dev,const enum display_pixel_format pf)217 static int ls0xx_set_pixel_format(const struct device *dev,
218 				  const enum display_pixel_format pf)
219 {
220 	if (pf == PIXEL_FORMAT_MONO01) {
221 		return 0;
222 	}
223 
224 	LOG_ERR("not supported");
225 	return -ENOTSUP;
226 }
227 
ls0xx_init(const struct device * dev)228 static int ls0xx_init(const struct device *dev)
229 {
230 	const struct ls0xx_config *config = dev->config;
231 
232 	if (!spi_is_ready_dt(&config->bus)) {
233 		LOG_ERR("SPI bus %s not ready", config->bus.bus->name);
234 		return -ENODEV;
235 	}
236 
237 #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios)
238 	if (!gpio_is_ready_dt(&config->disp_en_gpio)) {
239 		LOG_ERR("DISP port device not ready");
240 		return -ENODEV;
241 	}
242 	LOG_INF("Configuring DISP pin to OUTPUT_HIGH");
243 	gpio_pin_configure_dt(&config->disp_en_gpio, GPIO_OUTPUT_HIGH);
244 #endif
245 
246 #if DT_INST_NODE_HAS_PROP(0, extcomin_gpios)
247 	if (!gpio_is_ready_dt(&config->extcomin_gpio)) {
248 		LOG_ERR("EXTCOMIN port device not ready");
249 		return -ENODEV;
250 	}
251 	LOG_INF("Configuring EXTCOMIN pin");
252 	gpio_pin_configure_dt(&config->extcomin_gpio, GPIO_OUTPUT_LOW);
253 
254 	/* Start thread for toggling VCOM */
255 	k_tid_t vcom_toggle_tid = k_thread_create(&vcom_toggle_thread,
256 						  vcom_toggle_stack,
257 						  K_THREAD_STACK_SIZEOF(vcom_toggle_stack),
258 						  ls0xx_vcom_toggle,
259 						  (void *)config, NULL, NULL,
260 						  3, 0, K_NO_WAIT);
261 	k_thread_name_set(vcom_toggle_tid, "ls0xx_vcom");
262 #endif  /* DT_INST_NODE_HAS_PROP(0, extcomin_gpios) */
263 
264 	/* Clear display else it shows random data */
265 	return ls0xx_clear(dev);
266 }
267 
268 static const struct ls0xx_config ls0xx_config = {
269 	.bus = SPI_DT_SPEC_INST_GET(
270 		0, SPI_OP_MODE_MASTER | SPI_WORD_SET(8) |
271 		SPI_TRANSFER_LSB | SPI_CS_ACTIVE_HIGH |
272 		SPI_HOLD_ON_CS | SPI_LOCK_ON, 0),
273 #if DT_INST_NODE_HAS_PROP(0, disp_en_gpios)
274 	.disp_en_gpio = GPIO_DT_SPEC_INST_GET(0, disp_en_gpios),
275 #endif
276 #if DT_INST_NODE_HAS_PROP(0, extcomin_gpios)
277 	.extcomin_gpio = GPIO_DT_SPEC_INST_GET(0, extcomin_gpios),
278 #endif
279 };
280 
281 static const struct display_driver_api ls0xx_driver_api = {
282 	.blanking_on = ls0xx_blanking_on,
283 	.blanking_off = ls0xx_blanking_off,
284 	.write = ls0xx_write,
285 	.get_capabilities = ls0xx_get_capabilities,
286 	.set_pixel_format = ls0xx_set_pixel_format,
287 };
288 
289 DEVICE_DT_INST_DEFINE(0, ls0xx_init, NULL, NULL, &ls0xx_config, POST_KERNEL,
290 		      CONFIG_DISPLAY_INIT_PRIORITY, &ls0xx_driver_api);
291