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