1 /*
2 * Copyright (c) 2024 Savoir-faire Linux
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/logging/log.h>
8 LOG_MODULE_REGISTER(ssd1327, CONFIG_DISPLAY_LOG_LEVEL);
9
10 #include <string.h>
11 #include <zephyr/device.h>
12 #include <zephyr/init.h>
13 #include <zephyr/drivers/display.h>
14 #include <zephyr/drivers/gpio.h>
15 #include <zephyr/drivers/mipi_dbi.h>
16 #include <zephyr/kernel.h>
17
18 #include "ssd1327_regs.h"
19
20 #define SSD1327_ENABLE_VDD 0x01
21 #define SSD1327_ENABLE_SECOND_PRECHARGE 0x62
22 #define SSD1327_VCOMH_VOLTAGE 0x0f
23 #define SSD1327_PHASES_VALUE 0xf1
24 #define SSD1327_DEFAULT_PRECHARGE_V 0x08
25 #define SSD1327_UNLOCK_COMMAND 0x12
26
27 struct ssd1327_config {
28 const struct device *mipi_dev;
29 const struct mipi_dbi_config dbi_config;
30 uint16_t height;
31 uint16_t width;
32 uint8_t oscillator_freq;
33 uint8_t start_line;
34 uint8_t display_offset;
35 uint8_t multiplex_ratio;
36 uint8_t prechargep;
37 uint8_t remap_value;
38 bool color_inversion;
39 };
40
41 struct ssd1327_data {
42 uint8_t contrast;
43 uint8_t scan_mode;
44 };
45
ssd1327_write_bus_cmd(const struct device * dev,const uint8_t cmd,const uint8_t * data,size_t len)46 static inline int ssd1327_write_bus_cmd(const struct device *dev, const uint8_t cmd,
47 const uint8_t *data, size_t len)
48 {
49 const struct ssd1327_config *config = dev->config;
50 int err;
51
52 /* Values given after the memory register must be sent with pin D/C set to 0. */
53 /* Data is sent as a command following the mipi_cbi api */
54 err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config, cmd, NULL, 0);
55 if (err) {
56 return err;
57 }
58 for (size_t i = 0; i < len; i++) {
59 err = mipi_dbi_command_write(config->mipi_dev, &config->dbi_config,
60 data[i], NULL, 0);
61 if (err) {
62 return err;
63 }
64 }
65 mipi_dbi_release(config->mipi_dev, &config->dbi_config);
66
67 return 0;
68 }
69
ssd1327_set_timing_setting(const struct device * dev)70 static inline int ssd1327_set_timing_setting(const struct device *dev)
71 {
72 const struct ssd1327_config *config = dev->config;
73 uint8_t buf;
74
75 buf = SSD1327_PHASES_VALUE;
76 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PHASE_LENGTH, &buf, 1)) {
77 return -EIO;
78 }
79 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_OSC_FREQ, &config->oscillator_freq, 1)) {
80 return -EIO;
81 }
82 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PRECHARGE_PERIOD, &config->prechargep, 1)) {
83 return -EIO;
84 }
85 if (ssd1327_write_bus_cmd(dev, SSD1327_LINEAR_LUT, NULL, 0)) {
86 return -EIO;
87 }
88 buf = SSD1327_DEFAULT_PRECHARGE_V;
89 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_PRECHARGE_VOLTAGE, &buf, 1)) {
90 return -EIO;
91 }
92 buf = SSD1327_VCOMH_VOLTAGE;
93 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_VCOMH, &buf, 1)) {
94 return -EIO;
95 }
96 buf = SSD1327_ENABLE_SECOND_PRECHARGE;
97 if (ssd1327_write_bus_cmd(dev, SSD1327_FUNCTION_SELECTION_B, &buf, 1)) {
98 return -EIO;
99 }
100 buf = SSD1327_UNLOCK_COMMAND;
101 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_COMMAND_LOCK, &buf, 1)) {
102 return -EIO;
103 }
104
105 return 0;
106 }
107
ssd1327_set_hardware_config(const struct device * dev)108 static inline int ssd1327_set_hardware_config(const struct device *dev)
109 {
110 const struct ssd1327_config *config = dev->config;
111 uint8_t buf;
112
113 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_DISPLAY_START_LINE, &config->start_line, 1)) {
114 return -EIO;
115 }
116 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_DISPLAY_OFFSET, &config->display_offset, 1)) {
117 return -EIO;
118 }
119 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_NORMAL_DISPLAY, NULL, 0)) {
120 return -EIO;
121 }
122 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_SEGMENT_MAP_REMAPED, &config->remap_value, 1)) {
123 return -EIO;
124 }
125 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_MULTIPLEX_RATIO, &config->multiplex_ratio, 1)) {
126 return -EIO;
127 }
128 buf = SSD1327_ENABLE_VDD;
129 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_FUNCTION_A, &buf, 1)) {
130 return -EIO;
131 }
132
133 return 0;
134 }
135
ssd1327_resume(const struct device * dev)136 static int ssd1327_resume(const struct device *dev)
137 {
138 return ssd1327_write_bus_cmd(dev, SSD1327_DISPLAY_ON, NULL, 0);
139 }
140
ssd1327_suspend(const struct device * dev)141 static int ssd1327_suspend(const struct device *dev)
142 {
143 return ssd1327_write_bus_cmd(dev, SSD1327_DISPLAY_OFF, NULL, 0);
144 }
145
ssd1327_set_display(const struct device * dev)146 static int ssd1327_set_display(const struct device *dev)
147 {
148 const struct ssd1327_config *config = dev->config;
149 uint8_t x_position[] = {
150 0,
151 config->width - 1
152 };
153 uint8_t y_position[] = {
154 0,
155 config->height - 1
156 };
157
158 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_COLUMN_ADDR, x_position, sizeof(x_position))) {
159 return -EIO;
160 }
161 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_ROW_ADDR, y_position, sizeof(y_position))) {
162 return -EIO;
163 }
164 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_SEGMENT_MAP_REMAPED, &config->remap_value, 1)) {
165 return -EIO;
166 }
167
168 return 0;
169 }
170
ssd1327_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)171 static int ssd1327_write(const struct device *dev, const uint16_t x, const uint16_t y,
172 const struct display_buffer_descriptor *desc, const void *buf)
173 {
174 const struct ssd1327_config *config = dev->config;
175 struct display_buffer_descriptor mipi_desc;
176 int err;
177 size_t buf_len;
178 uint8_t x_position[] = { x, x + desc->width - 1 };
179 uint8_t y_position[] = { y, y + desc->height - 1 };
180
181 if (desc->pitch < desc->width) {
182 LOG_ERR("Pitch is smaller than width");
183 return -1;
184 }
185 mipi_desc.pitch = desc->pitch;
186
187 /* Following the datasheet, in the GDDRAM, two segment are split in one register */
188 buf_len = MIN(desc->buf_size, desc->height * desc->width / 2);
189 if (buf == NULL || buf_len == 0U) {
190 LOG_ERR("Display buffer is not available");
191 return -1;
192 }
193 mipi_desc.buf_size = buf_len;
194
195 if (desc->pitch > desc->width) {
196 LOG_ERR("Unsupported mode");
197 return -1;
198 }
199
200 if ((y & 0x7) != 0U) {
201 LOG_ERR("Unsupported origin");
202 return -1;
203 }
204 mipi_desc.height = desc->height;
205 mipi_desc.width = desc->width;
206
207 LOG_DBG("x %u, y %u, pitch %u, width %u, height %u, buf_len %u", x, y, desc->pitch,
208 desc->width, desc->height, buf_len);
209
210 err = ssd1327_write_bus_cmd(dev, SSD1327_SET_COLUMN_ADDR, x_position, sizeof(x_position));
211 if (err) {
212 return err;
213 }
214
215 err = ssd1327_write_bus_cmd(dev, SSD1327_SET_ROW_ADDR, y_position, sizeof(y_position));
216 if (err) {
217 return err;
218 }
219
220 err = mipi_dbi_write_display(config->mipi_dev, &config->dbi_config, buf, &mipi_desc,
221 PIXEL_FORMAT_MONO10);
222 if (err) {
223 return err;
224 }
225 return mipi_dbi_release(config->mipi_dev, &config->dbi_config);
226 }
227
ssd1327_set_contrast(const struct device * dev,const uint8_t contrast)228 static int ssd1327_set_contrast(const struct device *dev, const uint8_t contrast)
229 {
230 return ssd1327_write_bus_cmd(dev, SSD1327_SET_CONTRAST_CTRL, &contrast, 1);
231 }
232
ssd1327_get_capabilities(const struct device * dev,struct display_capabilities * caps)233 static void ssd1327_get_capabilities(const struct device *dev,
234 struct display_capabilities *caps)
235 {
236 const struct ssd1327_config *config = dev->config;
237
238 memset(caps, 0, sizeof(struct display_capabilities));
239 caps->x_resolution = config->width;
240 caps->y_resolution = config->height;
241 caps->supported_pixel_formats = PIXEL_FORMAT_MONO10;
242 caps->current_pixel_format = PIXEL_FORMAT_MONO10;
243 caps->screen_info = SCREEN_INFO_MONO_VTILED;
244 }
245
ssd1327_set_pixel_format(const struct device * dev,const enum display_pixel_format pf)246 static int ssd1327_set_pixel_format(const struct device *dev,
247 const enum display_pixel_format pf)
248 {
249 if (pf == PIXEL_FORMAT_MONO10) {
250 return 0;
251 }
252 LOG_ERR("Unsupported pixel format");
253 return -ENOTSUP;
254 }
255
ssd1327_init_device(const struct device * dev)256 static int ssd1327_init_device(const struct device *dev)
257 {
258 const struct ssd1327_config *config = dev->config;
259 uint8_t buf;
260
261 /* Turn display off */
262 if (ssd1327_suspend(dev)) {
263 return -EIO;
264 }
265
266 if (ssd1327_set_display(dev)) {
267 return -EIO;
268 }
269
270 if (ssd1327_set_contrast(dev, CONFIG_SSD1327_DEFAULT_CONTRAST)) {
271 return -EIO;
272 }
273
274 if (ssd1327_set_hardware_config(dev)) {
275 return -EIO;
276 }
277
278 buf = (config->color_inversion ?
279 SSD1327_SET_REVERSE_DISPLAY : SSD1327_SET_NORMAL_DISPLAY);
280 if (ssd1327_write_bus_cmd(dev, SSD1327_SET_ENTIRE_DISPLAY_OFF, &buf, 1)) {
281 return -EIO;
282 }
283
284 if (ssd1327_set_timing_setting(dev)) {
285 return -EIO;
286 }
287
288 if (ssd1327_resume(dev)) {
289 return -EIO;
290 }
291
292 return 0;
293 }
294
ssd1327_init(const struct device * dev)295 static int ssd1327_init(const struct device *dev)
296 {
297 const struct ssd1327_config *config = dev->config;
298
299 LOG_DBG("Initializing device");
300
301 if (!device_is_ready(config->mipi_dev)) {
302 LOG_ERR("MIPI Device not ready!");
303 return -EINVAL;
304 }
305
306 if (mipi_dbi_reset(config->mipi_dev, SSD1327_RESET_DELAY)) {
307 LOG_ERR("Failed to reset device!");
308 return -EIO;
309 }
310 k_msleep(SSD1327_RESET_DELAY);
311
312 if (ssd1327_init_device(dev)) {
313 LOG_ERR("Failed to initialize device!");
314 return -EIO;
315 }
316
317 return 0;
318 }
319
320 static DEVICE_API(display, ssd1327_driver_api) = {
321 .blanking_on = ssd1327_suspend,
322 .blanking_off = ssd1327_resume,
323 .write = ssd1327_write,
324 .set_contrast = ssd1327_set_contrast,
325 .get_capabilities = ssd1327_get_capabilities,
326 .set_pixel_format = ssd1327_set_pixel_format,
327 };
328
329 #define SSD1327_DEFINE(node_id) \
330 static struct ssd1327_data data##node_id; \
331 static const struct ssd1327_config config##node_id = { \
332 .mipi_dev = DEVICE_DT_GET(DT_PARENT(node_id)), \
333 .dbi_config = { .mode = MIPI_DBI_MODE_SPI_4WIRE, \
334 .config = MIPI_DBI_SPI_CONFIG_DT(node_id, \
335 SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | \
336 SPI_HOLD_ON_CS | SPI_LOCK_ON, 0), \
337 }, \
338 .height = DT_PROP(node_id, height), \
339 .width = DT_PROP(node_id, width), \
340 .oscillator_freq = DT_PROP(node_id, oscillator_freq), \
341 .display_offset = DT_PROP(node_id, display_offset), \
342 .start_line = DT_PROP(node_id, start_line), \
343 .multiplex_ratio = DT_PROP(node_id, multiplex_ratio), \
344 .prechargep = DT_PROP(node_id, prechargep), \
345 .remap_value = DT_PROP(node_id, remap_value), \
346 .color_inversion = DT_PROP(node_id, inversion_on), \
347 }; \
348 \
349 DEVICE_DT_DEFINE(node_id, ssd1327_init, NULL, &data##node_id, &config##node_id, \
350 POST_KERNEL, CONFIG_DISPLAY_INIT_PRIORITY, &ssd1327_driver_api);
351
352 DT_FOREACH_STATUS_OKAY(solomon_ssd1327fb, SSD1327_DEFINE)
353