From 38f2346bdba97238d0ea753d35e3e88514bbd18c Mon Sep 17 00:00:00 2001 From: Bill Gatliff Date: Sun, 24 Mar 2019 06:49:34 -0600 Subject: [PATCH] drivers/video/max7456.c: Support for the Maxim MAX7456 on-screen-display chip. --- drivers/video/Kconfig | 8 + drivers/video/Make.defs | 10 + drivers/video/README.max7456 | 65 ++ drivers/video/max7456.c | 1575 +++++++++++++++++++++++++++++++++ include/nuttx/video/max7456.h | 81 ++ 5 files changed, 1739 insertions(+) create mode 100644 drivers/video/README.max7456 create mode 100644 drivers/video/max7456.c create mode 100644 include/nuttx/video/max7456.h diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 9250a8bfc8..d75b92d35a 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -22,6 +22,14 @@ config FB_OVERLAY_BLIT depends on FB_OVERLAY default n +config VIDEO_MAX7456 + bool "Maxim 7456 Monochrome OSD" + default n + select SPI + ---help--- + Support for the Maxim 7456 monochrome on-screen display + multiplexer. + config VIDEO_OV2640 bool "OV2640 camera chip" default n diff --git a/drivers/video/Make.defs b/drivers/video/Make.defs index 4c673cdaf0..206c6a9af1 100644 --- a/drivers/video/Make.defs +++ b/drivers/video/Make.defs @@ -51,6 +51,16 @@ endif endif +# These video drivers depend on SPI support + +ifeq ($(CONFIG_SPI),y) + +ifeq ($(CONFIG_VIDEO_MAX7456),y) + CSRCS += max7456.c +endif + +endif + # Include video driver build support DEPPATH += --dep-path video diff --git a/drivers/video/README.max7456 b/drivers/video/README.max7456 new file mode 100644 index 0000000000..aa7843ce7b --- /dev/null +++ b/drivers/video/README.max7456 @@ -0,0 +1,65 @@ +drivers/video/README.max7456 + +23 March 2019 +Bill Gatliff + +The code in max7456.[ch] is a preliminary device driver for the MAX7456 analog +on-screen-display generator. This SPI slave chip is a popular feature in many +embedded devices due its low cost and power requirements. In particular, you +see it a lot on drone flight-management units. + +I use the term "preliminary" because at present, only the most rudimentary +capabilities of the chip are supported: + + * chip reset and startup + * read and write low-level chip control registers (DEBUG mode only) + * write CA (Character Address) data to the chip's framebuffer memory + +Some key missing features are, in no particular order: + + * VSYNC and HSYNC synchronization (prevents flicker) + * ability to update NVM (define custom character sets) + +If you have a factory-fresh chip, then the datasheet shows you what the factory +character data set looks like. If you've used the chip in other scenarios, +i.e. with Betaflight or similar, then your chip will almost certainly have had +the factory character data replaced with something application-specific. + +Either way, you'll probably want to update your character set before long. I +should probably get that working, unless you want to take a look at it +yoruself... + +The max7456_register() function starts things rolling. The omnibusf4 target +device provides an example (there may be others by the time you read this). + +In normal use, the driver creates a set of interfaces under /dev, i.e.: + +/dev/osd0/fb +/dev/osd0/raw (*) +/dev/osd0/vsync (*) + +* - not yet implemented + +By writing character data to the "fb" interface, you'll see data appear on the +display. NOTE that the data you write is NOT, for example, ASCII text: it is +the addresses of the characters in the chip's onboard character map. + +For example, if entry 42 in your onboard character map is a bitmap that looks +like "H", then when you write the ASCII "*" (decimal 42, hex 2a), you'll see +that "H" appear on your screen. + +If you build the code with the DEBUG macro defined, you will see a bunch more interfaces: + +/dev/osd0/VM0 +/dev/osd0/VM1 +/dev/osd/DMM +... +... + +These are interfaces to the low-level chip registers, which can be read and/or +written to help you figure out what's going on inside the chip. They're +probably more useful for me than you, but there they are in case I'm wrong +about that. + + +b.g. diff --git a/drivers/video/max7456.c b/drivers/video/max7456.c new file mode 100644 index 0000000000..f43adca1d9 --- /dev/null +++ b/drivers/video/max7456.c @@ -0,0 +1,1575 @@ +/**************************************************************************** + * drivers/video/max7456.c + * + * Support for the Maxim MAX7456 Single-Channel Monochrome On-Screen + * Display with Integrated EEPROM (datasheet 19-0576; Rev 1; 8/08). + * + * Copyright (C) 2019 Bill Gatliff. All rights reserved. + * Author: Bill Gatliff + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +/***************************************************************************** + * Theory of Operation + * + * The MAX7456 is a single-channel, monochrome, on-screen-display generator + * that accepts an NTSC or PAL video input signal, overlays user-defined + * character data, and renders the combined stream to CVBS (analog) output. + * The typical use case then forwards that CVBS output to a video transmitter, + * analog display, recording device, and/or other external components. + * + * The chip is fundamentally an SPI slave device with a register bank to + * configure the chip's analog components, update values in the display frame + * buffer, and modify the chip's onboard non-volatile character set. + * + * The MAX7456 must by necessity recover the video stream's hsync and vsync + * signals, as part of its normal operations. These signals are also made + * available at pins on the chip body, and may be used to synchronize updates + * of frame buffer data with the vertical-blanking period. Such + * synchronization prevents "glitches" during OSD updates. + * + * Up to 480 user-definable characters can be displayed at one time. Each + * 16-bit "character" is expressed an 8-bit index into the chip's onboard + * character set, followed by an 8-bit character attribute that controls the + * character's local background, blinking, and inversion. + * + * The overlaid characters may be distributed across 13 (NTSC) or 16 (PAL) + * rows of the visible display area. The attributes of each of those lines + * are also controllable on a line-by-line basis. + * + * OSD insertion is ultimately an analog process, and a few of the chip's + * control registers are provided to adjust the OSD multiplexer's rise and + * fall times. This is necesssary to strike the user's preferred balance + * between overlay sharpness and certain, undesirable display artifacts. The + * defaults are probably good enough to start with, though. + * + * Note: Although we use the term "frame buffer", we cannot use the NuttX + * standard /dev/fbN interface because our buffer memory is accessible only + * across SPI. This is an inexpensive, slow, simple chip, and you wouldn't use + * it for intensive work, but you WOULD use it on a memory-constrained + * device. We keep our RAM footprint small by not keeping a local copy of the + * framebuffer data. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Enables debug-related interfaces. Leave undefined otherwise. */ + +#define DEBUG 1 + +/* Sets bit @n */ + +#define BIT(n) (1 << (n)) + +/* Creates a mask of @m bits, i.e. MASK(2) -> 00000011 */ + +#define MASK(m) (BIT((m) + 1) - 1) + +/* Masks and shifts @v into bit field @m */ + +#define TO_BITFIELD(m,v) ((v) & MASK(m ##__WIDTH) << (m ##__SHIFT)) + +/* Un-masks and un-shifts bit field @m from @v */ + +#define FROM_BITFIELD(m,v) (((v) >> (m ##__SHIFT)) & MASK(m ##__WIDTH)) + +/* SPI read/write codes */ + +#define SPI_REG_READ 0x80 +#define SPI_REG_WRITE 0 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Register file description. */ + +enum mx7_regaddr_e +{ + VM0 = 0, /* video mode (config) 0 */ + VM0__PAL = BIT(6), + VM0__SYNCSEL__SHIFT = 4, + VM0__SYNCSEL__WIDTH = 2, + VM0__ENABLE = BIT(3), + VM0__VSYNC_EN = BIT(2), + VM0__RESET = BIT(1), + VM0__VBUF_EN = BIT(0), + + VM1 = 1, /* video mode (config) 1 */ + VM1__GRAY = BIT(7), + VM1__OSD_PCT__SHIFT = 4, + VM1__OSD_PCT__WIDTH = 3, + VM1__BT__SHIFT = 2, + VM1__BT__WIDTH = 2, + VM1__BD__SHIFT = 0, + VM1__BD__WIDTH = 2, + + HOS = 2, /* horizontal position */ + HOS__HPOS__SHIFT = 0, + HOS__HPOS__WIDTH = 6, + + VOS = 3, /* vertical position */ + VOS__VPOS__SHIFT = 0, + VOS__VPOS__WIDTH = 5, + + DMM = 4, /* display memory mode */ + DMM__8BIT = BIT(6), + DMM__LBC = BIT(5), + DMM__BLK = BIT(4), + DMM__INV = BIT(3), + DMM__CA__SHIFT = 3, /* character attribute bit field */ + DMM__CA__WIDTH = 3, /* (shorthand for lcb, blk, and inv) */ + DMM__CLEAR = BIT(2), + DMM__VCLEAR = BIT(1), + DMM__AUTOINC = BIT(0), + + DMAH = 5, /* display memory address register, high */ + DMAH__ATTR = BIT(1), + DMAH__ADDRBIT8__SHIFT = 0, + DMAH__ADDRBIT8__WIDTH = 1, + + DMAL = 6, /* display memory address register, low */ + DMAL__ADDR__SHIFT = 0, + DMAL__ADDR__WIDTH = 8, + + DMDI = 7, /* display memory data in */ + DMDI__SHIFT = 0, + DMDI__WIDTH = 8, + + CMM = 8, /* character memory mode */ + + CMAH = 9, /* character memory address, high */ + CMAH__SHIFT = 0, + CMAH__WIDTH = 6, + + CMAL = 0xa, /* character memory address, low */ + CMAL__ADDR__SHIFT = 0, + CMAL__ADDR__WIDTH = 6, + + CMDI = 0xb, /* character memory data in */ + + OSDM = 0xc, /* osd insertion mux */ + OSDM__RISET__SHIFT = 3, /* rise time */ + OSDM__RISET__WIDTH = 3, + OSDM__SWITCHT__SHIFT = 0, /* switching time */ + OSDM__SWITCHT__WIDTH = 3, + + RB0 = 0x10, /* row N brightness */ + RB1 = (RB0 + 1), + RB2 = (RB0 + 2), + RB3 = (RB0 + 4), + RB4 = (RB0 + 5), + RB6 = (RB0 + 6), + RB7 = (RB0 + 7), + RB8 = (RB0 + 8), + RB9 = (RB0 + 9), + RB10 = (RB0 + 10), + RB11 = (RB0 + 11), + RB12 = (RB0 + 12), + RB13 = (RB0 + 13), + RB14 = (RB0 + 14), + RB15 = (RB0 + 15), + + OSDBL = 0x6c, /* osd black level */ + OSDBL__DISABLE = BIT(4), + OSDBL__PRESET__SHIFT = 0, /* note: value must be preserved during writes */ + OSDBL__PRESET__WIDTH = 4, + + STAT = 0xa0, /* status (ro) */ + STAT__INRESET = BIT(6), /* 1 = chip is performing power-on reset */ + STAT__CHARUNAVAIL = BIT(5), /* 1 = character memory unavailable for writes */ + STAT__NVSYNC = BIT(4), /* 0 = "active during vertical sync time" */ + STAT__NHSYNC = BIT(3), /* 0 = "active during horizontal sync time" */ + STAT__LOS = BIT(2), /* 1 = no sync (after 32 missing input video lines) */ + STAT__NTSC = BIT(1), /* 1 = ntsc video stream detected */ + STAT__PAL = BIT(0), /* 1 = pal video stream detected */ + + DMDO = 0xb0, /* data memory data out */ /* ro */ + CMD0 = 0xc0, /* character memolry data out */ /* ro */ +}; + +struct path_name_map_s +{ + uint8_t addr; + FAR const char *path; +}; + +#define PATH_MAP_ENTRY(node) { .addr = (node), .path = "" #node "" } + +enum mx7_interface_e +{ + FB, /* 8-bit ASCII interface (or as best we can manage) */ + RAW, /* 16-bit interface in chip's native format */ + VSYNC /* blocks until vertical blanking interval */ +}; + +static struct path_name_map_s node_map[] = +{ + PATH_MAP_ENTRY(FB), + PATH_MAP_ENTRY(RAW), + PATH_MAP_ENTRY(VSYNC) +}; + +#define NODE_MAP_LEN (sizeof(node_map) / sizeof(*node_map)) + +#if defined(DEBUG) +/* Maps between register names and addresses */ + +static struct path_name_map_s reg_name_map[] = +{ + PATH_MAP_ENTRY(VM0), + PATH_MAP_ENTRY(VM1), + PATH_MAP_ENTRY(HOS), + PATH_MAP_ENTRY(VOS), + PATH_MAP_ENTRY(DMM), + PATH_MAP_ENTRY(DMAH), + PATH_MAP_ENTRY(DMAL), + PATH_MAP_ENTRY(DMDI), + PATH_MAP_ENTRY(CMM), + PATH_MAP_ENTRY(CMAH), + PATH_MAP_ENTRY(CMAL), + PATH_MAP_ENTRY(CMDI), + PATH_MAP_ENTRY(OSDM), + PATH_MAP_ENTRY(RB0), + PATH_MAP_ENTRY(RB1), + PATH_MAP_ENTRY(RB2), + PATH_MAP_ENTRY(RB3), + PATH_MAP_ENTRY(RB4), + PATH_MAP_ENTRY(RB6), + PATH_MAP_ENTRY(RB7), + PATH_MAP_ENTRY(RB8), + PATH_MAP_ENTRY(RB9), + PATH_MAP_ENTRY(RB10), + PATH_MAP_ENTRY(RB11), + PATH_MAP_ENTRY(RB12), + PATH_MAP_ENTRY(RB13), + PATH_MAP_ENTRY(RB14), + PATH_MAP_ENTRY(RB15), + PATH_MAP_ENTRY(OSDBL), + PATH_MAP_ENTRY(STAT), + PATH_MAP_ENTRY(DMDO), + PATH_MAP_ENTRY(CMD0) +}; +#endif + +#define REG_NAME_MAP_LEN (sizeof(reg_name_map) / sizeof(*reg_name_map)) + +/* Used to manage the device. No user-serviceable parts inside. */ + +struct mx7_dev_s +{ + mutex_t lock; /* mutex for this structure */ + struct mx7_config_s config; /* board-specific information */ + + uint8_t ca; /* current character attribute (lbc, blink, etc.) */ + +#if defined(DEBUG) + char debug[2]; /* stash for debugging-related output */ +#endif +}; + +/**************************************************************************** + * Private Function Function Prototypes + ****************************************************************************/ + +static int mx7_open(FAR struct file *filep); +static int mx7_close(FAR struct file *filep); +static ssize_t mx7_read(FAR struct file *filep, FAR char *buf, size_t len); +static ssize_t mx7_write(FAR struct file *filep, FAR const char *buf, + size_t len); +static int mx7_ioctl(FAR struct file *filep, int cmd, unsigned long arg); + +#if defined(DEBUG) +static int mx7_debug_open(FAR struct file *filep); +static int mx7_debug_close(FAR struct file *filep); +static ssize_t mx7_debug_read(FAR struct file *filep, FAR char *buf, + size_t len); +static ssize_t mx7_debug_write(FAR struct file *filep, FAR const char *buf, + size_t len); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* General user interface operations. */ + +static const struct file_operations g_mx7_fops = +{ +#ifndef CONFIG_DISABLE_POLL + .poll = NULL, +#endif +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + .unlink = NULL, +#endif + .open = mx7_open, + .close = mx7_close, + .read = mx7_read, + .write = mx7_write, + .ioctl = mx7_ioctl +}; + +#if defined(DEBUG) + +/* Debug-only interface, mostly for direct register access. */ + +static const struct file_operations g_mx7_debug_fops = +{ +#ifndef CONFIG_DISABLE_POLL + .poll = NULL, +#endif +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + .unlink = NULL, +#endif + .open = mx7_debug_open, + .close = mx7_debug_close, + .read = mx7_debug_read, + .write = mx7_debug_write, +}; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Translates an interface name name to its associated mx7_interface_e + * enumerator + */ + +static int node_from_name(FAR const char *name) +{ + for (int n = 0; n < NODE_MAP_LEN; n++) + { + if (!strcmp(name, node_map[n].path)) + { + return node_map[n].addr; + } + } + + return -1; +} + +/* Translates a register name to its associated address */ + +static int regaddr_from_name(FAR const char *name) +{ + for (int n = 0; n < REG_NAME_MAP_LEN; n++) + { + if (!strcmp(name, reg_name_map[n].path)) + { + return reg_name_map[n].addr; + } + } + + return -1; +} + +/* NOTE : + * + * In all of the following code, functions named with a double leading + * underscore '__' must be invoked ONLY if the mx7_dev_s lock is + * already held. Failure to do this might cause the transaction to get + * interrupted, which will likely confuse the data you're trying to send. + * + * The mx7_dev_s lock is NOT the same thing as, i.e. the SPI master + * interface lock: the latter protects the bus interface hardware + * (which may have other SPI devices attached), the former protects + * our chip and its associated data. + */ + +/**************************************************************************** + * Name: __mx7_read_reg + * + * Description: + * Reads @len bytes into @buf from @dev, starting at register address + * @addr. This is a low-level function used for reading a sequence of one or + * more register values, and isn't usually called directly unless you REALLY + * know what you are doing. Consider one of the register-specific helper + * functions defined below whenever possible. + * + * Note: The caller must hold @dev->lock before calling this function. + * + * Input parameters: + * @dev - the target device's handle + * @addr - starting register address + * @buf - where to store the register values + * @len - number of registers to read + * + * Return value: + * Returns number of bytes read, or a negative errno. + ****************************************************************************/ + +static int __mx7_read_reg(FAR struct mx7_dev_s *dev, + enum mx7_regaddr_e addr, + FAR uint8_t *buf, uint8_t len) +{ + int ret; + FAR struct spi_dev_s *spi = dev->config.spi; + int id = dev->config.spi_devid; + + /* We'll probably return the number of bytes asked for. */ + + ret = len; + + /* Grab the SPI master controller, and set the mode. + * + * Per datasheet, SCLK is always max 10MHz. + */ + + SPI_LOCK(spi, true); + SPI_SETMODE(spi, SPIDEV_MODE0); + SPI_SETFREQUENCY(spi, 10000000); + + /* Select the chip. */ + + SPI_SELECT(spi, id, true); + + /* Send the read request. */ + + SPI_SEND(spi, addr | SPI_REG_READ); + + /* Clock in the data. */ + + while (0 != len--) + { + *buf++ = (uint8_t) (SPI_SEND(spi, 0xff)); + } + + /* Deselect the chip, release the SPI master. */ + + SPI_SELECT(spi, id, false); + SPI_LOCK(spi, false); + + return ret; +} + +/**************************************************************************** + * Name: __mx7_write_reg + * + * Description: + * Writes @len bytes from @buf to @dev, starting at @addr. This is a + * low-level function used for updating a sequence of one or more register + * values, and it DOES NOT check that the register being requested is + * write-capable. This function isn't called directly unless you REALLY know + * what you are doing. + * + * Consider one of the register-specific helper functions defined below + * whenever possible. If a helper function for the register you desire to + * write is not defined, it's probably because that register is read-only. + * + * Note: The caller must hold @dev->lock before calling this function. + * + * Input parameters: + * @dev - the target device's handle + * @addr - starting register address + * @buf - byte sequence to write + * @len - length of @buf, number of bytes to write + * + * Return value: + * Returns number of bytes written, or a negative errno. + ****************************************************************************/ + +static int __mx7_write_reg(FAR struct mx7_dev_s *dev, + enum mx7_regaddr_e addr, + FAR const uint8_t *buf, uint8_t len) +{ + int ret = len; + FAR struct spi_dev_s *spi = dev->config.spi; + int id = dev->config.spi_devid; + + /* Grab and configure the SPI master device. */ + + SPI_LOCK(spi, true); + SPI_SETMODE(spi, SPIDEV_MODE0); + SPI_SETFREQUENCY(spi, 10000000); + + /* Select the chip. */ + + SPI_SELECT(spi, id, true); + + /* Send the write request. */ + + SPI_SEND(spi, addr | SPI_REG_WRITE); + + /* Send the data. */ + + while (0 != len--) + { + SPI_SEND(spi, *buf++); + } + + /* Release the chip and SPI master. */ + + SPI_SELECT(spi, id, false); + SPI_LOCK(spi, false); + + return ret; +} + +/**************************************************************************** + * Name: __mx7_read_reg__stat + * + * Description: + * Reads the contents of the STAT register. + * + * Return value: + * Returns the value in STAT, or negative errno. + ****************************************************************************/ + +static inline int __mx7_read_reg__stat(FAR struct mx7_dev_s *dev) +{ + uint8_t val = 0xff; + int ret; + + ret = __mx7_read_reg(dev, STAT, &val, sizeof(val)); + + /* Return the error code, if an error occurred. */ + + if (ret < 0) + { + return ret; + } + + /* Return the register value. */ + + return val; +} + +/**************************************************************************** + * Name: __mx7_read_reg__dmm + * + * Description: + * Reads the contents of the DMM register. + * + * Return value: + * Returns the value held in in DMM, or negative errno. + ****************************************************************************/ + +static inline int __mx7_read_reg__dmm(FAR struct mx7_dev_s *dev) +{ + uint8_t val = 0xff; + int ret; + + ret = __mx7_read_reg(dev, DMM, &val, sizeof(val)); + if (ret < 0) + { + return ret; + } + + return val; +} + +/**************************************************************************** + * Name: __mx7_wait_reset + * + * Description: + * Waits until the chip finishes its reset activities. + ****************************************************************************/ + +static inline void __mx7_wait_reset(FAR struct mx7_dev_s *dev) +{ + int stat = 0; /* contents of STAT register */ + int dmm = 0; /* contents of DMM register */ + + do + { + /* If we're here, a reset command has probably just been issued; + * wait 100usec before checking, per the datasheet. + */ + + up_udelay(100); + + stat = __mx7_read_reg__stat(dev); + dmm = __mx7_read_reg__dmm(dev); + } + while ((stat & STAT__INRESET) || (dmm & DMM__CLEAR)); +} + +/**************************************************************************** + * Name: __mx7_write_reg__vm0 + * + * Description: + * Writes @val to VM0. A simple helper around __mx7_write_reg(). + * + * Return value: + * Returns the number of bytes written (always 1), or a negative errno. + ****************************************************************************/ + +static inline int __mx7_write_reg__vm0(FAR struct mx7_dev_s *dev, uint8_t val) +{ + return __mx7_write_reg(dev, VM0, &val, sizeof(val)); +} + +/**************************************************************************** + * Name: __mx7_read_reg__osdbl + * + * Description: + * Returns the contents of OSDBL. A simple helper around __mx7_read_reg(). + * + * Return value: + * Returns the register value, or a negative errno. + ****************************************************************************/ + +static inline int __mx7_read_reg__osdbl(FAR struct mx7_dev_s *dev) +{ + uint8_t val = 0xff; + int ret; + + ret = __mx7_read_reg(dev, OSDBL, &val, sizeof(val)); + + if (ret < 0) + { + return ret; + } + + return val; +} + +/**************************************************************************** + * Name: __mx7_read_reg__osdbl + * + * Description: + * Returns the contents of OSDBL. A simple helper around __mx7_read_reg(). + * + * Return value: + * Returns the register value, or a negative errno. + * + ****************************************************************************/ + +static inline int __mx7_write_reg__osdbl(FAR struct mx7_dev_s *dev, + uint8_t val) +{ + return __mx7_write_reg(dev, OSDBL, &val, sizeof(val)); +} + +/**************************************************************************** + * Name: __mx7_read_reg__dmm + * + * Description: + * Returns the contents of DMM. A simple helper around __mx7_read_reg(). + * + * Return value: + * Returns the register value, or a negative errno. + * + ****************************************************************************/ + +static inline int __mx7_write_reg__dmm(FAR struct mx7_dev_s *dev, uint8_t val) +{ + return __mx7_write_reg(dev, DMM, &val, sizeof(val)); +} + +/**************************************************************************** + * Name: __mx7_read_reg__dmdi + * + * Description: + * Returns the contents of DMDI. A simple helper around __mx7_read_reg(). + * + * Return value: + * Returns the register value, or a negative errno. + * + ****************************************************************************/ + +static inline int __mx7_write_reg__dmdi(FAR struct mx7_dev_s *dev, + uint8_t val) +{ + return __mx7_write_reg(dev, DMDI, &val, sizeof(val)); +} + +/**************************************************************************** + * Name: __mx7_read_reg__dmah + * + * Description: + * Returns the contents of DMAH. A simple helper around __mx7_read_reg(). + * + * Return value: + * Returns the register value, or a negative errno. + * + ****************************************************************************/ + +static inline int __mx7_write_reg__dmah(FAR struct mx7_dev_s *dev, + uint8_t val) +{ + return __mx7_write_reg(dev, DMAH, &val, sizeof(val)); +} + +/**************************************************************************** + * Name: __mx7_read_reg__dmal + * + * Description: + * Returns the contents of DMAL. A simple helper around __mx7_read_reg(). + * + * Return value: + * Returns the register value, or a negative errno. + * + ****************************************************************************/ + +static inline int __mx7_write_reg__dmal(FAR struct mx7_dev_s *dev, + uint8_t val) +{ + return __mx7_write_reg(dev, DMAL, &val, sizeof(val)); +} + +/**************************************************************************** + * Name: __lock + * + * Description: + * Locks the @dev data structure (mutex) to protect it against concurrent + * access. This is necessary, because @dev has some state information in it + * that has to be kept consistent with the chip. This lock also protects + * operations that must not be interrupted by other access to the chip. + * + * Use this function before calling one of the lock-dependent helper + * functions defined above (there are some defined below here, too). + * + ****************************************************************************/ + +static void inline __lock(FAR struct mx7_dev_s *dev) +{ + nxmutex_lock(&dev->lock); +} + +/**************************************************************************** + * Name: __unlock + * + * Description: + * Unlocks the @dev data structure (mutex). + * + * Use this function after calling one of the lock-dependent helper + * functions defined above (there are some defined below here, too). + * + ****************************************************************************/ + +static void inline __unlock(FAR struct mx7_dev_s *dev) +{ + nxmutex_unlock(&dev->lock); +} + +/**************************************************************************** + * Name: mx7_reset + * + * Description: + * Asserts a RESET command in the chip, and waits for it to finish. Except + * for NVM and the OSD brightness trim, this action restores all register + * values in the chip to their factory defaults. + * + ****************************************************************************/ + +static void mx7_reset(FAR struct mx7_dev_s *dev) +{ + __lock(dev); + + /* Make sure we aren't already in RESET. */ + + __mx7_wait_reset(dev); + + /* Issue the reset command. We do this regardless of what we found + * above, because by calling this function the user is requesting us + * to issue a reset; we don't care if/why the chip might already + * have been in reset. + */ + + __mx7_write_reg__vm0(dev, VM0__RESET); + + /* Wait for all reset-related activities to finish. */ + + __mx7_wait_reset(dev); + + /* All done. */ + + __unlock(dev); +} + +/************************************************************************ + * Name: __write_fb + * + * Description: + * Writes a stream of bytes to character address memory. We use the chip's + * "16-bit auto-increment mode", in order to make this operation as fast + * as possible. All of the bytes written are given the same attribute @ca. + * + * This operation is best performed with the CS held, so we do all of the + * SPI heavy-lifting ourselves here. This function is comparable to a + * giant __write_reg_N(). + * + * Input parameters: + * buf - character addresses (data) to write + * len - length of @buf + * ca - character attribute, see the DMM register for details + * pos - starting address, 0 = upper-left corner of the display + * + * Returned value: + * Returns the number of bytes written, or a negative errno. + ****************************************************************************/ + +static ssize_t __write_fb(FAR struct mx7_dev_s *dev, + FAR const uint8_t *buf, size_t len, + uint8_t ca, size_t pos) +{ + ssize_t ret = len; + FAR struct spi_dev_s *spi = dev->config.spi; + int id = dev->config.spi_devid; + + /* Configure the bus and grab the chip as usual. */ + + SPI_LOCK(spi, true); + SPI_SETMODE(spi, SPIDEV_MODE0); + SPI_SETFREQUENCY(spi, 10000000); + SPI_SELECT(spi, id, true); + + while (len != 0) + { + /* Thus sayeth the datasheet (pp. 39-40): + * + * "When in 16-Bit [Auto-Increment] Operating Mode: + * + * 1) Write DMAH[0] = X to select the MSB and DMAL[7:0] = XX to + * select the lower order address bits of the starting address + * for auto-increment operation. This address determines the + * location of the first character on the display (see Figures + * 10 and 21)." + */ + + SPI_SEND(spi, DMAH | SPI_REG_WRITE); + SPI_SEND(spi, TO_BITFIELD(DMAH__ADDRBIT8, (pos >> 8))); + SPI_SEND(spi, DMAL | SPI_REG_WRITE); + SPI_SEND(spi, TO_BITFIELD(DMAL__ADDR, pos)); + + /* "2) Write DMM[0] = 1 to set the auto-increment mode. + * + * 3) Write DMM[6] = 0 to set the 16-bit operating mode. + * + * 4) Write DMM[5:3] = XXX to set the Local Background Control + * (LBC), Blink (BLK) and Invert (INV) attribute bits that will + * be applied to all characters." + */ + + SPI_SEND(spi, DMM | SPI_REG_WRITE); + SPI_SEND(spi, DMM__AUTOINC | TO_BITFIELD(DMM__CA, ca)); + + /* "5) Write CA [Character Address: the index into the chip's onboard + * NVM character map] data in the intended character order to + * display text on the screen. It will be stored along with a + * Character Attribute byte derived from DMM[5:3]. See Figure + * 19. This is the single byte operation. The DMDI[7:0] address + * is automatically set by autoincrement mode. The display memory + * address is automatically incremented following the write + * operation until the final display memory address is reached." + */ + + while (len != 0) + { + /* Send the byte to the DMDI register. The "auto-increment" + * mode will update DMAH and DMAL for us. + */ + + SPI_SEND(spi, DMDI | SPI_REG_WRITE); + SPI_SEND(spi, *buf); + + /* Check if we just exited auto-increment mode. */ + + if (*buf == 0xff) + { + /* An embedded 0xff terminates auto-increment mode, + * and since we've already sent it, pause here to + * deal with it. + * + * Betaflight, et. al just skip the byte and + * continue, and then retrace their steps later. I + * think it's a better workflow to just deal with it + * now. Plus, there's only a 1/256 chance of there + * being such a byte anyway, and if performance ends + * up being a problem then the user can move the CA + * to a different index in their NVM map. + */ + + break; + } + else + { + /* It was an ordinary byte, so we're still in + * auto-increment mode; count it and keep going. + */ + + buf++; + pos++; + len--; + } + } + + /* (Use of while() here instead of if() catches repeated 0xff's while + * we're already out of auto-increment mode. Since you mustached, + * this shaves a transaction or two when they occur.) + */ + + while (len != 0 && *buf == 0xff) + { + /* We're out of the auto-increment loop but still have data + * remaining, which means there's an 0xff in the data + * stream. We must send it the hard way, but we can still use + * the attribute byte already stored in DMM. + */ + + SPI_SEND(spi, DMAH | SPI_REG_WRITE); + SPI_SEND(spi, TO_BITFIELD(DMAH__ADDRBIT8, (pos >> 8))); + SPI_SEND(spi, DMAL | SPI_REG_WRITE); + SPI_SEND(spi, TO_BITFIELD(DMAL__ADDR, pos)); + SPI_SEND(spi, DMDI | SPI_REG_WRITE); + SPI_SEND(spi, *buf); + + /* Now we can retire the byte. */ + + buf++; + pos++; + len--; + } + } + + /* "6) Write CA = FFh to terminate the auto-increment mode." + * + * (We can do this safely even if we aren't in auto-increment mode, so we + * don't need to check.) + */ + + SPI_SEND(spi, DMDI | SPI_REG_WRITE); + SPI_SEND(spi, 0xff); + + /* The datasheet suggests that the chip will drop DMM[1] when it leaves + * auto-increment mode, but I don't see that happening. Let's force it to + * drop here just in case, so as to not not confuse future DMDI writes. + */ + + SPI_SEND(spi, DMM | SPI_REG_WRITE); + SPI_SEND(spi, DMM__AUTOINC | TO_BITFIELD(DMM__CA, ca)); + + /* And, finally, we're all done. */ + + SPI_SELECT(spi, id, false); + SPI_LOCK(spi, false); + + return ret; +} + +/**************************************************************************** + * Name: mx7_open + * + * Description: + * The usual open() interface for user accesses. + * + * Note: we don't deal with multiple users trying to access this interface at + * the same time. Until further notice, you probably should just not do that. + * + * It's not as simple as just prohibiting concurrent opens or reads with a + * mutex: there are legit reasons for concurrent access, but they must be + * treated carefully in this interface lest a partial reader end up with a + * mixture of old and new side-effects. This will make some users unhappy. + * + ****************************************************************************/ + +static int mx7_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mx7_dev_s *dev = inode->i_private; + + /* Reset any leftover CA from a previous operation. */ + + dev->ca = 0; + + return 0; +} + +/**************************************************************************** + * Name: mx7_close + * + * Description: + * The usual file-operations close() method. + ****************************************************************************/ + +static int mx7_close(FAR struct file *filep) +{ + UNUSED(filep); + return 0; +} + +/**************************************************************************** + * Name: mx7_read + * + * Description: + * The usual file-operations read() method. I don't know what such an + * operation would mean in general, so we do nothing here. + * + * TODO: One idea is to have interfaces allowing the user to discover + * details of our capabilities: display size, PAL vs. NTSC, etc., but I + * would want to have more experience with other chips before deciding how + * to best generalize those things. + ****************************************************************************/ + +static ssize_t mx7_read(FAR struct file *filep, FAR char *buf, size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mx7_dev_s *dev = inode->i_private; + UNUSED(inode); + UNUSED(dev); + return 0; +} + +/**************************************************************************** + * Name: mx7_write_fb + * + * Description: + * The usual file-operations write() method for the ".../fb" interface. + * The user is redirected here by the frontend write() helper defined + * below. + * + * We send @len bytes from @buf to the chip's character address array, + * starting at the current file position as stored in @filep->f_pos. Users + * may adjust this value beforehand by calling seek() in the usual + * way. (Position 0 is the upper-left corner of the display window.) + * + * Note: The contents of @buf aren't ASCII data, they're indices into the + * chip's onboard NVM character data. (It is possible to make those look + * like ASCII data, but that's not generally how the chip is used because + * it's a big waste of NVM.) + * + * Returned Value: + * Returns the number of bytes written, or negative errno. + * + ****************************************************************************/ + +static ssize_t mx7_write_fb(FAR struct file *filep, FAR const char *buf, + size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mx7_dev_s *dev = inode->i_private; + ssize_t ret; + + __lock(dev); + ret = __write_fb(dev, (FAR uint8_t *)buf, len, dev->ca, filep->f_pos); + __unlock(dev); + + return ret; +} + +/**************************************************************************** + * Name: mx7_write + * + * Description: + * A "frontend write() helper" that redirects the user's write() request + * to the correct handler. We are otherwise an ordinary file-operations + * write() function. + * + * We use the approach you see here so that we don't have to have one + * distinct function (and a separate file_operations structure) for each of + * the many interfaces we're likely to create for interacting with this chip + * in its various useful ways. This schema also lets us re-use the interface + * code internally (see the test-pattern generator at startup.) + * + * In general, any function we call from here uses the combination of seek() + * and write() to implement a zero-copy frame buffer. The seek() parameter + * sets the current cursor position, and successive write()s provide the + * character data starting at that position. + * + * TODO: At the moment, we have no mechanism for setting the character + * attribute (the LBC, BLK, and INV fields in DMM) for the data arriving + * here. Fortunately, the default value of '0', asserted in open(), works + * for the basic stuff. + * + * The above isn't a hard problem to solve, I just don't need to solve it + * right now. And, I don't know what the most convenient solution would look + * like: the obvious choice is ioctl(), but I don't like ioctl() because I + * can't test it from the command line. + * + * One idea is to have "fb", "blink", "inv", and other entry points for + * writing data with specific attributes. That has a nice feel to it, + * actually... + * + ****************************************************************************/ + +static ssize_t mx7_write(FAR struct file *filep, + FAR const char *buf, + size_t len) +{ + FAR struct inode *inode = filep->f_inode; + + /* Which interface are they using? */ + + switch (node_from_name(inode->i_name)) + { + case FB : + /* The "here is some stuff to display" interface */ + + return mx7_write_fb(filep, buf, len); + + case RAW : + /* The "raw" interface (unimplemented) */ + + break; /* return mx7_write_raw(filep, buf, len); */ + + default: + /* Someday we'll have others, I'm sure... */ + + break; + } + + /* If you get here, we have no idea what you are asking for. */ + + return -EINVAL; +} + +/**************************************************************************** + * Name: mx7_ioctl + * + * Description: + * Does nothing, because I don't like ioctls. + ****************************************************************************/ + +static int mx7_ioctl(FAR struct file *filep, + int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mx7_dev_s *dev = inode->i_private; + + UNUSED(inode); + UNUSED(dev); + return -ENOTTY; /* unsupported ioctl */ +} + +#if defined(DEBUG) + +/**************************************************************************** + * Name: uint8_to_hex + * + * Description: + * Converts an 8-bit integer value to its ascii-hex representation. Values + * less than 16 are right-justified and padded with zero. + * + * Input parameters: + * @n - integer value to convert + * @buf - two-byte buffer to store the converted representation + * + * Returned value: + * Always returns 2. + * + ****************************************************************************/ + +static int uint8_to_hex(uint8_t n, FAR char *buf) +{ + static FAR const char *hexchar = "0123456789abcdef"; + + buf[0] = hexchar[(n >> 4) & 0xf]; + buf[1] = hexchar[n & 0xf]; + return 2; +} + +/**************************************************************************** + * Name: hex_to_uint8 + * + * Description: + * Converts a two-byte, ascii-hex string to its integer value. + * + * Input parameters: + * @buf - nul-terminated sequence of ascii-hex string characters + * + * Returned value: + * Returns the converted value. + * + ****************************************************************************/ + +static int hex_to_uint8(FAR const char *buf) +{ + /* Interpret as hex even without the leading "0x". */ + + return strtol(buf, NULL, 16); +} + +/**************************************************************************** + * Name: mx7_debug_open + * + * Description: + * Ordinary file-operations open() for debug-related interfaces. + * + ****************************************************************************/ + +static int mx7_debug_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mx7_dev_s *dev = inode->i_private; + FAR const char *name = inode->i_name; + + UNUSED(inode); + UNUSED(dev); + UNUSED(name); + return 0; +} + +/**************************************************************************** + * Name: mx7_debug_close + * + * Description: + * Ordinary file-operations close() for debug-related interfaces. + * + ****************************************************************************/ + +static int mx7_debug_close(FAR struct file *filep) +{ + return 0; +} + +/**************************************************************************** + * Name: mx7_debug_read + * + * Description: + * Semi-ordinary file-operations read() method. Returns the value in the + * eponymous register, formatted as ascii hex. This allows users to observe + * raw hardware register values, like this: + * + * $ cat /dev/osd0/DMM + * e5 + * + * This same function is used for all registers, which are distinguished + * by @filep->f_inode->i_name, i.e. there is a "/dev/osd0/DMM", + * "/dev/osd0/VM0", etc., and reads from all of those interfaces arrive + * here. + * + * Utilities like cat(1) will exit automatically at EOF, which can be tricky + * to deliver at the right time. We achieve this by reading the associated + * register value only once, when filep->f_pos is at the beginning of the + * "file" we're emulating. The value obtained is stored in dev->debug[], and + * we work our way through that and increment the "file position" + * accordingly to keep track (because the user may ask for only one byte + * at a time, and our register values require two bytes to express as + * ascii-hex text). + * + * When we reach the end of dev->debug[], we return EOF. If the user wants a + * fresh copy, they can either close and reopen the interface, or move the + * file pointer back to 0 via a seek operation. + * + ****************************************************************************/ + +static ssize_t mx7_debug_read(FAR struct file *filep, FAR char *buf, + size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mx7_dev_s *dev = inode->i_private; + FAR const char *name = inode->i_name; + FAR const char *orig_buf = buf; + int ret = 0; + int addr = 0; + uint8_t val = 0; + + /* If we've already sent them a copy of the register value, don't re-send + * it until they ask for a fresh one by either reopening the interface, or + * doing a seek() to reset the cursor. This causes cat(1), etc. to exit + * nicely. + */ + + if (filep->f_pos >= sizeof(dev->debug)) + { + return 0; /* 0 == "eof" */ + } + + /* Populate the register value "cache" if needed. */ + + if (filep->f_pos == 0) + { + /* Map the interface name to its associated register address. */ + + addr = regaddr_from_name(name); + + /* Read the register. */ + + __lock(dev); + ret = __mx7_read_reg(dev, addr, &val, 1); + __unlock(dev); + + if (ret != 1) + { + return ret; + } + + /* Save the value to our local cache. */ + + uint8_to_hex(val, dev->debug); + } + + /* Return as many bytes as we have that will fit. */ + + while (len-- != 0 && filep->f_pos < sizeof(dev->debug)) + { + *buf++ = dev->debug[filep->f_pos++]; + } + + return buf - orig_buf; +} + +/**************************************************************************** + * Name: mx7_debug_write + * + * Description: + * Semi-ordinary file-operations write() method, for all debugging + * interfaces. + * + * Specifically, we allow users to assert new register values, by sending + * us ascii-hex strings: + * + * $ echo 3e > /dev/osd0/VM0 + * + ****************************************************************************/ + +static ssize_t mx7_debug_write(FAR struct file *filep, FAR const char *buf, + size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct mx7_dev_s *dev = inode->i_private; + FAR const char *name = inode->i_name; + + /* Map the incoming interface name to the associated register address. */ + + int addr = regaddr_from_name(name); + + /* Convert from ascii-hex to binary. */ + + uint8_t val = hex_to_uint8(buf); + + /* Write the register value. */ + + __mx7_write_reg(dev, addr, &val, 1); + + return len; +} +#endif + +/**************************************************************************** + * Name: add_interface + * + * Description: + * Creates an interface named "@path/@name", and registers it. If @name is + * NULL, the interface is named just "@path" instead. + * + * Input parameters: + * path - The full path to the interface to register. E.g., "/dev/osd0" + * name - Entry underneath @path (making the latter a directory) + * fops - File operations for the interface + * mode - Access permisisons + * private - Opaque pointer to forward to the file operation handlers + * + * Returned value: + * Zero on success, negative errno otherwise. + * + ****************************************************************************/ + +static int add_interface(FAR const char *path, + FAR const char *name, + FAR const struct file_operations *fops, + mode_t mode, + FAR void *private) +{ + char buf[128]; + + /* Start with calling @path the interface name. */ + + strcpy(buf, path); + + /* Is the interface actually in a directory named @path? */ + + if (name != NULL) + { + /* Convert @path to a directory name. */ + + strcat(buf, "/"); + + /* Append the real interface name. */ + + strcat(buf, name); + } + + /* Register the interface in the usual way. NuttX will build the + * (pseudo-)directory for us. + */ + + return register_driver(buf, fops, mode, private); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: max7456_register + * + * Description: + * Creates awareness of a max7456 chip, and builds a user interface to it. + * + * Input parameters: + * path - The full path to the interface to register. E.g., "/dev/osd0" + * config - Configuration information + * + * Returned value: + * Zero on success, negative errno otherwise. + * + ****************************************************************************/ + +int max7456_register(FAR const char *path, FAR struct mx7_config_s *config) +{ + FAR struct mx7_dev_s *dev = NULL; + int ret = 0; + int osdbl = 0; + + /* Without config info, we can't do anything. */ + + if (config == NULL) + { + return -EINVAL; + } + + /* Initialize the device structure. */ + + dev = (FAR struct mx7_dev_s *)kmm_malloc(sizeof(struct mx7_dev_s)); + if (dev == NULL) + { + return -ENOMEM; + } + + memset(dev, 0, sizeof(*dev)); + nxmutex_init(&dev->lock); + + /* Keep a copy of the config structure, in case the caller discards theirs. */ + + dev->config = *config; + + /* Reset the display, to give it a clean initial state. */ + + mx7_reset(dev); + + /* Turn the display on. */ + + /* Note: we don't _really_ need to lock this, because nobody can see our + * device yet. But since we're using the lock-requiring functions below, I'm + * doing it anyway for consistency. + */ + + __lock(dev); + + /* Thus sayeth the datasheet (pp. 38): + * + * "The following two steps enable viewing of the OSD image. These steps are + * not required to read from or write to the display memory: + * + * 1) Write VM0[3] = 1 to enable the display of the OSD image." + */ + + __mx7_write_reg__vm0(dev, VM0__ENABLE); + + /* "2) Write OSDBL[4] = 0 to enable automatic OSD black level control [Note: + * there is no "manual" control]. This ensures the correct OSD image + * brightness. This register contains 4 factory-preset bits [3:0] that + * must not be changed. Therefore, when changing bit 4, first read + * OSDBL[7:0], modify bit 4, and then write back the updated byte." + */ + + osdbl = __mx7_read_reg__osdbl(dev); + osdbl &= ~OSDBL__DISABLE; + __mx7_write_reg__osdbl(dev, osdbl); + + /* Create device nodes for the ordinary user interfaces: + * + * /dev/osd0/fb + * /dev/osd0/raw + * /dev/osd0/vsync + */ + + for (int n = 0; ret >= 0 && n < NODE_MAP_LEN; n++) + { + ret = add_interface(path, node_map[n].path, &g_mx7_fops, 0666, dev); + } + +#if defined(DEBUG) + /* Add the register-debugging entries. These are device nodes with names + * that match the associated register, which developers can read or write + * through to see what the hardware is doing. Not useful in everyday + * activities. + */ + + for (int n = 0; ret >= 0 && n < REG_NAME_MAP_LEN; n++) + { + ret = add_interface(path, reg_name_map[n].path, &g_mx7_debug_fops, + 0666, dev); + } +#endif + + if (ret < 0) + { + snerr("ERROR: Failed to register max7456 interface: %d\n", ret); + nxmutex_destroy(&dev->lock); + kmm_free(dev); + return ret; + } + +#if defined(DEBUG) + /* For testing, display a test pattern of sorts. When this sequence is + * longer than 254 bytes, we get a 0xff in the stream; this confirms that + * __write_fb() can handle that situation properly. + */ + + uint8_t buf[300]; + for (int n = 0; n < sizeof(buf); n++) + { + buf[n] = n; + } + + __write_fb(dev, buf, sizeof(buf), 0, 0); +#endif + + /* Release the device to the world. */ + + __unlock(dev); + + return 0; +} diff --git a/include/nuttx/video/max7456.h b/include/nuttx/video/max7456.h new file mode 100644 index 0000000000..515152aaf5 --- /dev/null +++ b/include/nuttx/video/max7456.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * include/nuttx/video/max7456.h + * + * Support for the Maxim MAX7456 Single-Channel Monochrome On-Screen + * Display with Integrated EEPROM (datasheet 19-0576; Rev 1; 8/08). + * + * Copyright (C) 2019 Bill Gatliff. All rights reserved. + * Author: Bill Gatliff + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + *****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_VIDEO_MAX7456_H +#define __INCLUDE_NUTTX_VIDEO_MAX7456_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +struct spi_dev_s; + +struct mx7_config_s +{ + int spi_devid; + FAR struct spi_dev_s *spi; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: max7456_register + * + * Description: + * Registers an max7456 chip, and creates an interface 'devpath' + * + * Input Parameters: + * path - The full path to the interface to register. E.g., "/dev/osd0" + * config - Configuration information + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int max7456_register(FAR const char *path, FAR struct mx7_config_s *config); + +#endif /*__INCLUDE_NUTTX_VIDEO_MAX7456_H */