1 /*
2 * Copyright (c) 2022 Jimmy Ou <yanagiis@gmail.com>
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT maxim_max7219
8
9 /**
10 * @file
11 * @brief MAX7219 LED display driver
12 *
13 * This driver map the segment as x, digit as y.
14 *
15 * A MAX7219 has 8x8 pixels.
16 * Two MAX7219s (with cascading) have 8x16 pixels.
17 * So on and so forth.
18 *
19 * Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf
20 *
21 * Limitations:
22 * 1. This driver only implements no-decode mode.
23 */
24
25 #include <stddef.h>
26
27 #include <zephyr/drivers/display.h>
28 #include <zephyr/drivers/spi.h>
29 #include <zephyr/logging/log.h>
30 #include <zephyr/sys/byteorder.h>
31 #include <zephyr/sys/util.h>
32 #include <zephyr/kernel.h>
33 LOG_MODULE_REGISTER(max7219, CONFIG_DISPLAY_LOG_LEVEL);
34
35 #define MAX7219_SEGMENTS_PER_DIGIT 8
36 #define MAX7219_DIGITS_PER_DEVICE 8
37
38 /* clang-format off */
39
40 /* MAX7219 registers and fields */
41 #define MAX7219_REG_NOOP 0x00
42 #define MAX7219_NOOP 0x00
43
44 #define MAX7219_REG_DECODE_MODE 0x09
45 #define MAX7219_NO_DECODE 0x00
46
47 #define MAX7219_REG_INTENSITY 0x0A
48
49 #define MAX7219_REG_SCAN_LIMIT 0x0B
50
51 #define MAX7219_REG_SHUTDOWN 0x0C
52 #define MAX7219_SHUTDOWN_MODE 0x00
53 #define MAX7219_LEAVE_SHUTDOWN_MODE 0x01
54
55 #define MAX7219_REG_DISPLAY_TEST 0x0F
56 #define MAX7219_LEAVE_DISPLAY_TEST_MODE 0x00
57 #define MAX7219_DISPLAY_TEST_MODE 0x01
58
59 /* clang-format on */
60
61 struct max7219_config {
62 struct spi_dt_spec spi;
63 uint32_t num_cascading;
64 uint8_t intensity;
65 uint8_t scan_limit;
66 };
67
68 struct max7219_data {
69 /* Keep all digit_buf for all cascading MAX7219 */
70 uint8_t *digit_buf;
71 uint8_t *tx_buf;
72 };
73
max7219_transmit_all(const struct device * dev,const uint8_t addr,const uint8_t value)74 static int max7219_transmit_all(const struct device *dev, const uint8_t addr, const uint8_t value)
75 {
76 const struct max7219_config *dev_config = dev->config;
77 struct max7219_data *dev_data = dev->data;
78
79 const struct spi_buf tx_buf = {
80 .buf = dev_data->tx_buf,
81 .len = dev_config->num_cascading * 2,
82 };
83 const struct spi_buf_set tx_bufs = {
84 .buffers = &tx_buf,
85 .count = 1U,
86 };
87
88 for (int i = 0; i < dev_config->num_cascading; i++) {
89 dev_data->tx_buf[i * 2] = addr;
90 dev_data->tx_buf[i * 2 + 1] = value;
91 }
92
93 return spi_write_dt(&dev_config->spi, &tx_bufs);
94 }
95
max7219_transmit_one(const struct device * dev,const uint8_t max7219_idx,const uint8_t addr,const uint8_t value)96 static int max7219_transmit_one(const struct device *dev, const uint8_t max7219_idx,
97 const uint8_t addr, const uint8_t value)
98 {
99 const struct max7219_config *dev_config = dev->config;
100 struct max7219_data *dev_data = dev->data;
101
102 const struct spi_buf tx_buf = {
103 .buf = dev_data->tx_buf,
104 .len = dev_config->num_cascading * 2,
105 };
106 const struct spi_buf_set tx_bufs = {
107 .buffers = &tx_buf,
108 .count = 1U,
109 };
110
111 for (int i = 0; i < dev_config->num_cascading; i++) {
112 if (i != (dev_config->num_cascading - 1 - max7219_idx)) {
113 dev_data->tx_buf[i * 2] = MAX7219_REG_NOOP;
114 dev_data->tx_buf[i * 2 + 1] = MAX7219_NOOP;
115 continue;
116 }
117
118 dev_data->tx_buf[i * 2] = addr;
119 dev_data->tx_buf[i * 2 + 1] = value;
120 }
121
122 return spi_write_dt(&dev_config->spi, &tx_bufs);
123 }
124
next_pixel(uint8_t * mask,uint8_t * data,const uint8_t ** buf)125 static inline uint8_t next_pixel(uint8_t *mask, uint8_t *data, const uint8_t **buf)
126 {
127 *mask <<= 1;
128 if (!*mask) {
129 *mask = 0x01;
130 *data = *(*buf)++;
131 }
132 return *data & *mask;
133 }
134
skip_pixel(uint8_t * mask,uint8_t * data,const uint8_t ** buf,uint16_t count)135 static inline void skip_pixel(uint8_t *mask, uint8_t *data, const uint8_t **buf, uint16_t count)
136 {
137 while (count--) {
138 next_pixel(mask, data, buf);
139 }
140 }
141
max7219_write(const struct device * dev,const uint16_t x,const uint16_t y,const struct display_buffer_descriptor * desc,const void * buf)142 static int max7219_write(const struct device *dev, const uint16_t x, const uint16_t y,
143 const struct display_buffer_descriptor *desc, const void *buf)
144 {
145 const struct max7219_config *dev_config = dev->config;
146 struct max7219_data *dev_data = dev->data;
147
148 const uint16_t max_width = MAX7219_SEGMENTS_PER_DIGIT;
149 const uint16_t max_height = dev_config->num_cascading * MAX7219_DIGITS_PER_DEVICE;
150
151 /*
152 * MAX7219 only supports PIXEL_FORMAT_MONO01. 1 bit stands for 1 pixel.
153 */
154 __ASSERT((desc->pitch * desc->height) <= (desc->buf_size * 8U), "Input buffer to small");
155 __ASSERT(desc->width <= desc->pitch, "Pitch is smaller then width");
156 __ASSERT(desc->pitch <= max_width, "Pitch in descriptor is larger than screen size");
157 __ASSERT(desc->height <= max_height, "Height in descriptor is larger than screen size");
158 __ASSERT(x + desc->pitch <= max_width,
159 "Writing outside screen boundaries in horizontal direction");
160 __ASSERT(y + desc->height <= max_height,
161 "Writing outside screen boundaries in vertical direction");
162
163 if (desc->width > desc->pitch || (desc->pitch * desc->height) > (desc->buf_size * 8U)) {
164 return -EINVAL;
165 }
166
167 if ((x + desc->pitch) > max_width || (y + desc->height) > max_height) {
168 return -EINVAL;
169 }
170
171 const uint16_t end_x = x + desc->width;
172 const uint16_t end_y = y + desc->height;
173 const uint8_t *byte_buf = buf;
174 const uint16_t to_skip = desc->pitch - desc->width;
175 uint8_t mask = 0;
176 uint8_t data = 0;
177
178 for (uint16_t py = y; py < end_y; ++py) {
179 const uint8_t max7219_idx = py / MAX7219_DIGITS_PER_DEVICE;
180 const uint8_t digit_idx = py % MAX7219_DIGITS_PER_DEVICE;
181 uint8_t segment = dev_data->digit_buf[py];
182 int ret;
183
184 for (uint16_t px = x; px < end_x; ++px) {
185 WRITE_BIT(segment, px, next_pixel(&mask, &data, &byte_buf));
186 }
187
188 skip_pixel(&mask, &data, &byte_buf, to_skip);
189
190 /* led register address begins from 1 */
191 ret = max7219_transmit_one(dev, max7219_idx, digit_idx + 1, segment);
192 if (ret < 0) {
193 return ret;
194 }
195
196 dev_data->digit_buf[y] = segment;
197 }
198
199 return 0;
200 }
201
max7219_set_brightness(const struct device * dev,const uint8_t brightness)202 static int max7219_set_brightness(const struct device *dev, const uint8_t brightness)
203 {
204 int ret;
205
206 /*
207 * max7219 supports intensity value from 0x0 to 0xF.
208 * map the brightness from [0, 255] to [0, 15]
209 */
210 ret = max7219_transmit_all(dev, MAX7219_REG_INTENSITY, brightness >> 4);
211 if (ret < 0) {
212 LOG_ERR("Failed to set brightness");
213 return ret;
214 }
215
216 return 0;
217 }
218
max7219_set_pixel_format(const struct device * dev,const enum display_pixel_format format)219 static int max7219_set_pixel_format(const struct device *dev,
220 const enum display_pixel_format format)
221 {
222 ARG_UNUSED(dev);
223
224 switch (format) {
225 case PIXEL_FORMAT_MONO01:
226 return 0;
227 default:
228 return -ENOTSUP;
229 }
230 }
231
max7219_set_orientation(const struct device * dev,const enum display_orientation orientation)232 static int max7219_set_orientation(const struct device *dev,
233 const enum display_orientation orientation)
234 {
235 ARG_UNUSED(dev);
236
237 switch (orientation) {
238 case DISPLAY_ORIENTATION_NORMAL:
239 return 0;
240 default:
241 return -ENOTSUP;
242 }
243 }
244
max7219_get_capabilities(const struct device * dev,struct display_capabilities * caps)245 static void max7219_get_capabilities(const struct device *dev, struct display_capabilities *caps)
246 {
247 const struct max7219_config *dev_config = dev->config;
248
249 caps->x_resolution = MAX7219_SEGMENTS_PER_DIGIT;
250 caps->y_resolution = MAX7219_DIGITS_PER_DEVICE * dev_config->num_cascading;
251 caps->supported_pixel_formats = PIXEL_FORMAT_MONO01;
252 caps->screen_info = 0;
253 caps->current_pixel_format = PIXEL_FORMAT_MONO01;
254 caps->current_orientation = DISPLAY_ORIENTATION_NORMAL;
255 }
256
257 static const struct display_driver_api max7219_api = {
258 .write = max7219_write,
259 .set_brightness = max7219_set_brightness,
260 .get_capabilities = max7219_get_capabilities,
261 .set_pixel_format = max7219_set_pixel_format,
262 .set_orientation = max7219_set_orientation,
263 };
264
max7219_init(const struct device * dev)265 static int max7219_init(const struct device *dev)
266 {
267 const struct max7219_config *dev_config = dev->config;
268 struct max7219_data *dev_data = dev->data;
269 int ret;
270
271 if (!spi_is_ready_dt(&dev_config->spi)) {
272 LOG_ERR("SPI device not ready");
273 return -ENODEV;
274 }
275
276 /* turn off all leds */
277 memset(dev_data->digit_buf, 0,
278 dev_config->num_cascading * MAX7219_DIGITS_PER_DEVICE * sizeof(uint8_t));
279
280 ret = max7219_transmit_all(dev, MAX7219_REG_DISPLAY_TEST, MAX7219_LEAVE_DISPLAY_TEST_MODE);
281 if (ret < 0) {
282 LOG_ERR("Failed to disable display test");
283 return ret;
284 }
285
286 ret = max7219_transmit_all(dev, MAX7219_REG_DECODE_MODE, MAX7219_NO_DECODE);
287 if (ret < 0) {
288 LOG_ERR("Failed to set decode mode");
289 return ret;
290 }
291
292 ret = max7219_transmit_all(dev, MAX7219_REG_INTENSITY, dev_config->intensity);
293 if (ret < 0) {
294 LOG_ERR("Failed to set global brightness");
295 return ret;
296 }
297
298 ret = max7219_transmit_all(dev, MAX7219_REG_SCAN_LIMIT, dev_config->scan_limit);
299 if (ret < 0) {
300 LOG_ERR("Failed to set scan limit");
301 return ret;
302 }
303
304 ret = max7219_transmit_all(dev, MAX7219_REG_SHUTDOWN, MAX7219_LEAVE_SHUTDOWN_MODE);
305 if (ret < 0) {
306 LOG_ERR("Failed to leave shutdown state");
307 return ret;
308 }
309
310 const struct display_buffer_descriptor desc = {
311 .buf_size = dev_config->num_cascading * MAX7219_DIGITS_PER_DEVICE,
312 .height = dev_config->num_cascading * MAX7219_DIGITS_PER_DEVICE,
313 .width = MAX7219_DIGITS_PER_DEVICE,
314 .pitch = MAX7219_DIGITS_PER_DEVICE,
315 };
316
317 ret = max7219_write(dev, 0, 0, &desc, dev_data->digit_buf);
318 if (ret < 0) {
319 return ret;
320 }
321
322 return 0;
323 }
324
325 #define DISPLAY_MAX7219_INIT(n) \
326 static uint8_t max7219_digit_data_##n[DT_INST_PROP(n, num_cascading) * \
327 MAX7219_DIGITS_PER_DEVICE]; \
328 static uint8_t max7219_tx_buf##n[DT_INST_PROP(n, num_cascading) * 2]; \
329 static struct max7219_data max7219_data_##n = { \
330 .digit_buf = max7219_digit_data_##n, \
331 .tx_buf = max7219_tx_buf##n, \
332 }; \
333 static const struct max7219_config max7219_config_##n = { \
334 .spi = SPI_DT_SPEC_INST_GET( \
335 n, SPI_OP_MODE_MASTER | SPI_WORD_SET(8U), 0U), \
336 .num_cascading = DT_INST_PROP(n, num_cascading), \
337 .intensity = DT_INST_PROP(n, intensity), \
338 .scan_limit = DT_INST_PROP(n, scan_limit), \
339 }; \
340 DEVICE_DT_INST_DEFINE(n, max7219_init, NULL, &max7219_data_##n, \
341 &max7219_config_##n, POST_KERNEL, \
342 CONFIG_DISPLAY_INIT_PRIORITY, &max7219_api);
343
344 DT_INST_FOREACH_STATUS_OKAY(DISPLAY_MAX7219_INIT)
345