/* * Copyright (c) 2022 Jimmy Ou * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT maxim_max7219 /** * @file * @brief MAX7219 LED display driver * * This driver map the segment as x, digit as y. * * A MAX7219 has 8x8 pixels. * Two MAX7219s (with cascading) have 8x16 pixels. * So on and so forth. * * Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf * * Limitations: * 1. This driver only implements no-decode mode. */ #include #include #include #include #include #include #include LOG_MODULE_REGISTER(max7219, CONFIG_DISPLAY_LOG_LEVEL); #define MAX7219_SEGMENTS_PER_DIGIT 8 #define MAX7219_DIGITS_PER_DEVICE 8 /* clang-format off */ /* MAX7219 registers and fields */ #define MAX7219_REG_NOOP 0x00 #define MAX7219_NOOP 0x00 #define MAX7219_REG_DECODE_MODE 0x09 #define MAX7219_NO_DECODE 0x00 #define MAX7219_REG_INTENSITY 0x0A #define MAX7219_REG_SCAN_LIMIT 0x0B #define MAX7219_REG_SHUTDOWN 0x0C #define MAX7219_SHUTDOWN_MODE 0x00 #define MAX7219_LEAVE_SHUTDOWN_MODE 0x01 #define MAX7219_REG_DISPLAY_TEST 0x0F #define MAX7219_LEAVE_DISPLAY_TEST_MODE 0x00 #define MAX7219_DISPLAY_TEST_MODE 0x01 /* clang-format on */ struct max7219_config { struct spi_dt_spec spi; uint32_t num_cascading; uint8_t intensity; uint8_t scan_limit; }; struct max7219_data { /* Keep all digit_buf for all cascading MAX7219 */ uint8_t *digit_buf; uint8_t *tx_buf; }; static int max7219_transmit_all(const struct device *dev, const uint8_t addr, const uint8_t value) { const struct max7219_config *dev_config = dev->config; struct max7219_data *dev_data = dev->data; const struct spi_buf tx_buf = { .buf = dev_data->tx_buf, .len = dev_config->num_cascading * 2, }; const struct spi_buf_set tx_bufs = { .buffers = &tx_buf, .count = 1U, }; for (int i = 0; i < dev_config->num_cascading; i++) { dev_data->tx_buf[i * 2] = addr; dev_data->tx_buf[i * 2 + 1] = value; } return spi_write_dt(&dev_config->spi, &tx_bufs); } static int max7219_transmit_one(const struct device *dev, const uint8_t max7219_idx, const uint8_t addr, const uint8_t value) { const struct max7219_config *dev_config = dev->config; struct max7219_data *dev_data = dev->data; const struct spi_buf tx_buf = { .buf = dev_data->tx_buf, .len = dev_config->num_cascading * 2, }; const struct spi_buf_set tx_bufs = { .buffers = &tx_buf, .count = 1U, }; for (int i = 0; i < dev_config->num_cascading; i++) { if (i != (dev_config->num_cascading - 1 - max7219_idx)) { dev_data->tx_buf[i * 2] = MAX7219_REG_NOOP; dev_data->tx_buf[i * 2 + 1] = MAX7219_NOOP; continue; } dev_data->tx_buf[i * 2] = addr; dev_data->tx_buf[i * 2 + 1] = value; } return spi_write_dt(&dev_config->spi, &tx_bufs); } static inline uint8_t next_pixel(uint8_t *mask, uint8_t *data, const uint8_t **buf) { *mask <<= 1; if (!*mask) { *mask = 0x01; *data = *(*buf)++; } return *data & *mask; } static inline void skip_pixel(uint8_t *mask, uint8_t *data, const uint8_t **buf, uint16_t count) { while (count--) { next_pixel(mask, data, buf); } } static int max7219_blanking_on(const struct device *dev) { ARG_UNUSED(dev); return -ENOTSUP; } static int max7219_blanking_off(const struct device *dev) { ARG_UNUSED(dev); return -ENOTSUP; } static int max7219_write(const struct device *dev, const uint16_t x, const uint16_t y, const struct display_buffer_descriptor *desc, const void *buf) { const struct max7219_config *dev_config = dev->config; struct max7219_data *dev_data = dev->data; const uint16_t max_width = MAX7219_SEGMENTS_PER_DIGIT; const uint16_t max_height = dev_config->num_cascading * MAX7219_DIGITS_PER_DEVICE; /* * MAX7219 only supports PIXEL_FORMAT_MONO01. 1 bit stands for 1 pixel. */ __ASSERT((desc->pitch * desc->height) <= (desc->buf_size * 8U), "Input buffer to small"); __ASSERT(desc->width <= desc->pitch, "Pitch is smaller then width"); __ASSERT(desc->pitch <= max_width, "Pitch in descriptor is larger than screen size"); __ASSERT(desc->height <= max_height, "Height in descriptor is larger than screen size"); __ASSERT(x + desc->pitch <= max_width, "Writing outside screen boundaries in horizontal direction"); __ASSERT(y + desc->height <= max_height, "Writing outside screen boundaries in vertical direction"); if (desc->width > desc->pitch || (desc->pitch * desc->height) > (desc->buf_size * 8U)) { return -EINVAL; } if ((x + desc->pitch) > max_width || (y + desc->height) > max_height) { return -EINVAL; } const uint16_t end_x = x + desc->width; const uint16_t end_y = y + desc->height; const uint8_t *byte_buf = buf; const uint16_t to_skip = desc->pitch - desc->width; uint8_t mask = 0; uint8_t data = 0; for (uint16_t py = y; py < end_y; ++py) { const uint8_t max7219_idx = py / MAX7219_DIGITS_PER_DEVICE; const uint8_t digit_idx = py % MAX7219_DIGITS_PER_DEVICE; uint8_t segment = dev_data->digit_buf[py]; int ret; for (uint16_t px = x; px < end_x; ++px) { WRITE_BIT(segment, px, next_pixel(&mask, &data, &byte_buf)); } skip_pixel(&mask, &data, &byte_buf, to_skip); /* led register address begins from 1 */ ret = max7219_transmit_one(dev, max7219_idx, digit_idx + 1, segment); if (ret < 0) { return ret; } dev_data->digit_buf[y] = segment; } return 0; } static int max7219_read(const struct device *dev, const uint16_t x, const uint16_t y, const struct display_buffer_descriptor *desc, void *buf) { ARG_UNUSED(dev); ARG_UNUSED(x); ARG_UNUSED(y); ARG_UNUSED(desc); ARG_UNUSED(buf); return -ENOTSUP; } static void *max7219_get_framebuffer(const struct device *dev) { ARG_UNUSED(dev); return NULL; } static int max7219_set_brightness(const struct device *dev, const uint8_t brightness) { int ret; /* * max7219 supports intensity value from 0x0 to 0xF. * map the brightness from [0, 255] to [0, 15] */ ret = max7219_transmit_all(dev, MAX7219_REG_INTENSITY, brightness >> 4); if (ret < 0) { LOG_ERR("Failed to set brightness"); return ret; } return 0; } static int max7219_set_contrast(const struct device *dev, const uint8_t contrast) { ARG_UNUSED(dev); ARG_UNUSED(contrast); return -ENOTSUP; } static int max7219_set_pixel_format(const struct device *dev, const enum display_pixel_format format) { ARG_UNUSED(dev); switch (format) { case PIXEL_FORMAT_MONO01: return 0; default: return -ENOTSUP; } } static int max7219_set_orientation(const struct device *dev, const enum display_orientation orientation) { ARG_UNUSED(dev); switch (orientation) { case DISPLAY_ORIENTATION_NORMAL: return 0; default: return -ENOTSUP; } } static void max7219_get_capabilities(const struct device *dev, struct display_capabilities *caps) { const struct max7219_config *dev_config = dev->config; caps->x_resolution = MAX7219_SEGMENTS_PER_DIGIT; caps->y_resolution = MAX7219_DIGITS_PER_DEVICE * dev_config->num_cascading; caps->supported_pixel_formats = PIXEL_FORMAT_MONO01; caps->screen_info = 0; caps->current_pixel_format = PIXEL_FORMAT_MONO01; caps->current_orientation = DISPLAY_ORIENTATION_NORMAL; } static const struct display_driver_api max7219_api = { .blanking_on = max7219_blanking_on, .blanking_off = max7219_blanking_off, .write = max7219_write, .read = max7219_read, .get_framebuffer = max7219_get_framebuffer, .set_brightness = max7219_set_brightness, .set_contrast = max7219_set_contrast, .get_capabilities = max7219_get_capabilities, .set_pixel_format = max7219_set_pixel_format, .set_orientation = max7219_set_orientation, }; static int max7219_init(const struct device *dev) { const struct max7219_config *dev_config = dev->config; struct max7219_data *dev_data = dev->data; int ret; if (!spi_is_ready_dt(&dev_config->spi)) { LOG_ERR("SPI device not ready"); return -ENODEV; } /* turn off all leds */ memset(dev_data->digit_buf, 0, dev_config->num_cascading * MAX7219_DIGITS_PER_DEVICE * sizeof(uint8_t)); ret = max7219_transmit_all(dev, MAX7219_REG_DISPLAY_TEST, MAX7219_LEAVE_DISPLAY_TEST_MODE); if (ret < 0) { LOG_ERR("Failed to disable display test"); return ret; } ret = max7219_transmit_all(dev, MAX7219_REG_DECODE_MODE, MAX7219_NO_DECODE); if (ret < 0) { LOG_ERR("Failed to set decode mode"); return ret; } ret = max7219_transmit_all(dev, MAX7219_REG_INTENSITY, dev_config->intensity); if (ret < 0) { LOG_ERR("Failed to set global brightness"); return ret; } ret = max7219_transmit_all(dev, MAX7219_REG_SCAN_LIMIT, dev_config->scan_limit); if (ret < 0) { LOG_ERR("Failed to set scan limit"); return ret; } ret = max7219_transmit_all(dev, MAX7219_REG_SHUTDOWN, MAX7219_LEAVE_SHUTDOWN_MODE); if (ret < 0) { LOG_ERR("Failed to leave shutdown state"); return ret; } const struct display_buffer_descriptor desc = { .buf_size = dev_config->num_cascading * MAX7219_DIGITS_PER_DEVICE, .height = dev_config->num_cascading * MAX7219_DIGITS_PER_DEVICE, .width = MAX7219_DIGITS_PER_DEVICE, .pitch = MAX7219_DIGITS_PER_DEVICE, }; ret = max7219_write(dev, 0, 0, &desc, dev_data->digit_buf); if (ret < 0) { return ret; } return 0; } #define DISPLAY_MAX7219_INIT(n) \ static uint8_t max7219_digit_data_##n[DT_INST_PROP(n, num_cascading) * \ MAX7219_DIGITS_PER_DEVICE]; \ static uint8_t max7219_tx_buf##n[DT_INST_PROP(n, num_cascading) * 2]; \ static struct max7219_data max7219_data_##n = { \ .digit_buf = max7219_digit_data_##n, \ .tx_buf = max7219_tx_buf##n, \ }; \ static const struct max7219_config max7219_config_##n = { \ .spi = SPI_DT_SPEC_INST_GET( \ n, SPI_OP_MODE_MASTER | SPI_WORD_SET(8U), 0U), \ .num_cascading = DT_INST_PROP(n, num_cascading), \ .intensity = DT_INST_PROP(n, intensity), \ .scan_limit = DT_INST_PROP(n, scan_limit), \ }; \ DEVICE_DT_INST_DEFINE(n, max7219_init, NULL, &max7219_data_##n, \ &max7219_config_##n, POST_KERNEL, \ CONFIG_DISPLAY_INIT_PRIORITY, &max7219_api); DT_INST_FOREACH_STATUS_OKAY(DISPLAY_MAX7219_INIT)