You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
9.5 KiB
344 lines
9.5 KiB
/* |
|
* Copyright (c) 2022 Jimmy Ou <yanagiis@gmail.com> |
|
* |
|
* 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 <stddef.h> |
|
|
|
#include <zephyr/drivers/display.h> |
|
#include <zephyr/drivers/spi.h> |
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/sys/byteorder.h> |
|
#include <zephyr/sys/util.h> |
|
#include <zephyr/kernel.h> |
|
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_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 too small"); |
|
__ASSERT(desc->width <= desc->pitch, "Pitch is smaller than 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_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_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 DEVICE_API(display, max7219_api) = { |
|
.write = max7219_write, |
|
.set_brightness = max7219_set_brightness, |
|
.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)
|
|
|