|  | /**************************************************************************** | 
|  | * drivers/lcd/ht16k33_14seg.c | 
|  | * | 
|  | * Licensed to the Apache Software Foundation (ASF) under one or more | 
|  | * contributor license agreements.  See the NOTICE file distributed with | 
|  | * this work for additional information regarding copyright ownership.  The | 
|  | * ASF licenses this file to you under the Apache License, Version 2.0 (the | 
|  | * "License"); you may not use this file except in compliance with the | 
|  | * License.  You may obtain a copy of the License at | 
|  | * | 
|  | *   http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | 
|  | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the | 
|  | * License for the specific language governing permissions and limitations | 
|  | * under the License. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /* Alphanumeric display driver for HOLTEK HT16K33 (and VINKA VK16K33 clone) | 
|  | * This driver is specific for a 0.54" 14-segment LED HT16K33 Backpack | 
|  | * module with 4 14-segment digits (2 Kingbright 5241AS display). | 
|  | * Note: the model I'm testing uses the VK16K33. | 
|  | * | 
|  | * This is how the displays are connected: | 
|  | * Left Display: Digit 1: Catode connected to COM3 | 
|  | * Left Display: Digit 2: Catode connected to COM2 | 
|  | * Right Display: Digit 1: Catode connected to COM1 | 
|  | * Right Display: Digit 2: Catode connected to COM0 | 
|  | * | 
|  | * 14-Segment  |   LED Controller | 
|  | * ------------------------------- | 
|  | * 8  - DP     |   ROW14 - 11 | 
|  | * 13 - p      |   ROW6  - 19 | 
|  | * 2  - n      |   ROW11 - 14 | 
|  | * 4  - m      |   ROW12 - 13 | 
|  | * 5  - l      |   ROW13 - 12 | 
|  | * 6  - k      |   ROW7  - 18 | 
|  | * 14 - j      |   ROW10 - 15 | 
|  | * 15 - h      |   ROW9  - 16 | 
|  | * 17 - g      |   ROW8  - 17 | 
|  | * 18 - f      |   ROW5  - 20 | 
|  | * 1  - e      |   ROW4  - 21 | 
|  | * 7  - d      |   ROW3  - 22 | 
|  | * 9  - c      |   ROW2  - 23 | 
|  | * 10 - b      |   ROW1  - 24 | 
|  | * 12 - a      |   ROW0  - 25 | 
|  | */ | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Included Files | 
|  | ****************************************************************************/ | 
|  |  | 
|  | #include <nuttx/config.h> | 
|  |  | 
|  | #include <stdlib.h> | 
|  | #include <errno.h> | 
|  | #include <debug.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #include <nuttx/kmalloc.h> | 
|  | #include <nuttx/mutex.h> | 
|  | #include <nuttx/signal.h> | 
|  | #include <nuttx/ascii.h> | 
|  | #include <nuttx/fs/fs.h> | 
|  | #include <nuttx/lcd/slcd_codec.h> | 
|  | #include <nuttx/lcd/slcd_ioctl.h> | 
|  | #include <nuttx/i2c/i2c_master.h> | 
|  | #include <nuttx/lcd/ht16k33.h> | 
|  |  | 
|  | #ifndef CONFIG_LIBC_SLCDCODEC | 
|  | # error please also select Library Routines, Segment LCD CODEC | 
|  | #endif | 
|  |  | 
|  | #if defined(CONFIG_I2C) && defined(CONFIG_LCD_HT16K33) | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Pre-processor Definitions | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /* I2C frequency */ | 
|  |  | 
|  | #ifndef CONFIG_HT16K33_I2C_FREQ | 
|  | #  define CONFIG_HT16K33_I2C_FREQ 400000 | 
|  | #endif | 
|  |  | 
|  | #ifndef CONFIG_LCD_HT16K33_NUMBER_MODULES | 
|  | #  define CONFIG_LCD_HT16K33_NUMBER_MODULES 1 | 
|  | #endif | 
|  |  | 
|  | #define HT16K33_MAX_ROW    1 | 
|  | #define HT16K33_MAX_COL    4 * CONFIG_LCD_HT16K33_NUMBER_MODULES | 
|  |  | 
|  | /* Device naming ************************************************************/ | 
|  |  | 
|  | #define DEVNAME_FMT    "/dev/slcd%d" | 
|  | #define DEVNAME_FMTLEN (9 + 3 + 1) | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Private Types | 
|  | ****************************************************************************/ | 
|  |  | 
|  | struct ht16k33_dev_s | 
|  | { | 
|  | FAR struct i2c_master_s *i2c; /* I2C interface */ | 
|  | uint8_t    row;               /* Current row position to write on display  */ | 
|  | uint8_t    col;               /* Current col position to write on display  */ | 
|  | uint8_t    buffer[HT16K33_MAX_COL]; | 
|  | bool       pendscroll; | 
|  | mutex_t    lock; | 
|  | }; | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Private Function Prototypes | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline void ht16k33_write_cmd(FAR struct ht16k33_dev_s *priv, | 
|  | int dev_id, uint8_t cmd); | 
|  |  | 
|  | static inline void ht16k33_write_data(FAR struct ht16k33_dev_s *priv, | 
|  | int dev_id, uint8_t cmd, | 
|  | uint8_t *values, int nbytes); | 
|  |  | 
|  | static inline void ht16k33_setcontrast(FAR struct ht16k33_dev_s *priv, | 
|  | int dev_id, int8_t contrast); | 
|  |  | 
|  | static void lcd_scroll_up(FAR struct ht16k33_dev_s *priv); | 
|  |  | 
|  | static void ht16k33_clear_display(FAR struct ht16k33_dev_s *priv); | 
|  |  | 
|  | /* Character driver methods */ | 
|  |  | 
|  | static ssize_t ht16k33_read(FAR struct file *filep, FAR char *buffer, | 
|  | size_t buflen); | 
|  | static ssize_t ht16k33_write(FAR struct file *filep, FAR const char *buffer, | 
|  | size_t buflen); | 
|  | static off_t   ht16k33_seek(FAR struct file *filep, off_t offset, | 
|  | int whence); | 
|  | static int     ht16k33_ioctl(FAR struct file *filep, int cmd, | 
|  | unsigned long arg); | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Private Data | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static const struct file_operations g_ht16k33fops = | 
|  | { | 
|  | NULL,           /* open */ | 
|  | NULL,           /* close */ | 
|  | ht16k33_read,   /* read */ | 
|  | ht16k33_write,  /* write */ | 
|  | ht16k33_seek,   /* seek */ | 
|  | ht16k33_ioctl,  /* ioctl */ | 
|  | }; | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Private Functions | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ht16k33_write_cmd | 
|  | * | 
|  | * Description: | 
|  | *   Write an Instruction command to HT16K33 | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline void ht16k33_write_cmd(FAR struct ht16k33_dev_s *priv, | 
|  | int dev_id, uint8_t cmd) | 
|  | { | 
|  | struct i2c_msg_s msg; | 
|  | uint8_t data[1]; | 
|  | int ret; | 
|  |  | 
|  | /* Prepare data to send */ | 
|  |  | 
|  | data[0] = cmd; | 
|  |  | 
|  | /* Setup the HT16K33 Command */ | 
|  |  | 
|  | msg.frequency = CONFIG_HT16K33_I2C_FREQ;   /* I2C frequency */ | 
|  | msg.addr      = HT16K33_I2C_ADDR + dev_id; /* 7-bit address */ | 
|  | msg.flags     = 0;                         /* Write transaction */ | 
|  | msg.buffer    = data;                      /* Transfer from this address */ | 
|  | msg.length    = 1;                         /* Send one byte */ | 
|  |  | 
|  | /* Perform the transfer */ | 
|  |  | 
|  | ret = I2C_TRANSFER(priv->i2c, &msg, 1); | 
|  | if (ret < 0) | 
|  | { | 
|  | lcderr("ERROR: I2C_TRANSFER failed: %d\n", ret); | 
|  | } | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ht16k33_write_data | 
|  | * | 
|  | * Description: | 
|  | *   Write a Data command to HT16K33 | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline void ht16k33_write_data(FAR struct ht16k33_dev_s *priv, | 
|  | int dev_id, uint8_t cmd, | 
|  | uint8_t *values, int nbytes) | 
|  | { | 
|  | struct i2c_msg_s msg; | 
|  | uint8_t data[16]; | 
|  | int ret; | 
|  | int i; | 
|  |  | 
|  | /* Prepare data to send */ | 
|  |  | 
|  | data[0] = cmd; | 
|  |  | 
|  | for (i = 0; i < nbytes; i++) | 
|  | { | 
|  | data[i + 1] = values[i]; | 
|  | } | 
|  |  | 
|  | /* Setup the message to write data to HT16K33 */ | 
|  |  | 
|  | msg.frequency = CONFIG_HT16K33_I2C_FREQ;   /* I2C frequency */ | 
|  | msg.addr      = HT16K33_I2C_ADDR + dev_id; /* 7-bit address */ | 
|  | msg.flags     = 0;                         /* Write transaction */ | 
|  | msg.buffer    = data;                      /* Transfer from here */ | 
|  | msg.length    = nbytes + 1;                /* Send cmd + nbytes */ | 
|  |  | 
|  | /* Perform the transfer */ | 
|  |  | 
|  | ret = I2C_TRANSFER(priv->i2c, &msg, 1); | 
|  | if (ret < 0) | 
|  | { | 
|  | lcderr("ERROR: I2C_TRANSFER failed: %d\n", ret); | 
|  | } | 
|  | } | 
|  |  | 
|  | static inline void ht16k33_setcontrast(FAR struct ht16k33_dev_s *priv, | 
|  | int dev_id, int8_t contrast) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | if (contrast < HT16K33_CONTRAST_MIN) | 
|  | { | 
|  | contrast = HT16K33_CONTRAST_MIN; | 
|  | } | 
|  | else if (contrast > HT16K33_CONTRAST_MAX) | 
|  | { | 
|  | contrast = HT16K33_CONTRAST_MAX; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < CONFIG_LCD_HT16K33_NUMBER_MODULES; i++) | 
|  | { | 
|  | ht16k33_write_cmd(priv, i, HT16K33_DIMMING_SET | (contrast & 0x0f)); | 
|  | } | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: lcd_getdata | 
|  | * | 
|  | * Description: | 
|  | *  Simulate reading data from LCD, we are reading from internal buffer | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline uint8_t lcd_getdata(FAR struct ht16k33_dev_s *priv) | 
|  | { | 
|  | uint8_t data; | 
|  | data = priv->buffer[priv->row * priv->col]; | 
|  | return data; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: rc2addr | 
|  | * | 
|  | * Description: | 
|  | *  This converts a row/column pair to a screen memory address. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline uint8_t rc2addr(FAR struct ht16k33_dev_s *priv) | 
|  | { | 
|  | /* Each module has 4 digits they correspond to these columns: | 
|  | * | 
|  | * col0: 0x00 - 0x01, col1: 0x02 - 0x03, | 
|  | * col2: 0x04 - 0x05, col3: 0x06 - 0x07 | 
|  | */ | 
|  |  | 
|  | return (priv->col % 4) * 0x02; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: addr2rc | 
|  | * | 
|  | * Description: | 
|  | *  This converts a screen memory address to a row/column pair. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline void addr2rc(FAR struct ht16k33_dev_s *priv, uint8_t addr, | 
|  | FAR uint8_t *row, FAR uint8_t *col) | 
|  | { | 
|  | *row = 0; | 
|  | *col = addr / 2; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: lcd_set_curpos | 
|  | * | 
|  | * Description: | 
|  | *  This sets the cursor position based on row, column addressing. | 
|  | * | 
|  | * Input Parameters: | 
|  | *  priv - device instance | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static void lcd_set_curpos(FAR struct ht16k33_dev_s *priv) | 
|  | { | 
|  | uint8_t addr; | 
|  | int dev_id; | 
|  |  | 
|  | addr = rc2addr(priv); | 
|  | dev_id = priv->col / 4; | 
|  |  | 
|  | /* Define the memory address position */ | 
|  |  | 
|  | ht16k33_write_cmd(priv, dev_id, HT16K33_DISP_DATA_ADDR | addr); | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: lcd_putdata | 
|  | * | 
|  | * Description: | 
|  | *  Write a byte to the LCD and update column/row position | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static inline void lcd_putdata(FAR struct ht16k33_dev_s *priv, uint8_t data) | 
|  | { | 
|  | uint8_t segment[2]; | 
|  | uint8_t addr; | 
|  | uint8_t cmd; | 
|  | int dev_id; | 
|  |  | 
|  | /* Get current display memory position */ | 
|  |  | 
|  | addr = rc2addr(priv); | 
|  |  | 
|  | /* Setup the memory command */ | 
|  |  | 
|  | cmd = HT16K33_DISP_DATA_ADDR | addr; | 
|  |  | 
|  | /* Get the segments setting */ | 
|  |  | 
|  | segment[0] = asciito14seg[data - 32] & 0xff; | 
|  | segment[1] = (asciito14seg[data - 32] & 0xff00) >> 8; | 
|  |  | 
|  | dev_id = priv->col / 4; | 
|  |  | 
|  | /* Send data to display */ | 
|  |  | 
|  | ht16k33_write_data(priv, dev_id, cmd, segment, 2); | 
|  |  | 
|  | /* Save it in the buffer because we cannot read from display */ | 
|  |  | 
|  | priv->buffer[priv->col * priv->row] = data; | 
|  |  | 
|  | /* Update col/row positions */ | 
|  |  | 
|  | priv->col++; | 
|  |  | 
|  | if (priv->col >= HT16K33_MAX_COL) | 
|  | { | 
|  | priv->col = 0; | 
|  | priv->row++; | 
|  | } | 
|  |  | 
|  | if (priv->row >= HT16K33_MAX_ROW) | 
|  | { | 
|  | priv->pendscroll = true; | 
|  | priv->row        = HT16K33_MAX_ROW - 1; | 
|  | } | 
|  |  | 
|  | /* Update cursor position */ | 
|  |  | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: lcd_scroll_up | 
|  | * | 
|  | * Description: | 
|  | *  Scroll the display up, and clear the new (last) line. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static void lcd_scroll_up(FAR struct ht16k33_dev_s *priv) | 
|  | { | 
|  | FAR uint8_t *data; | 
|  | int currow; | 
|  | int curcol; | 
|  |  | 
|  | data = kmm_malloc(HT16K33_MAX_COL); | 
|  | if (NULL == data) | 
|  | { | 
|  | lcdinfo("Failed to allocate buffer in lcd_scroll_up()\n"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | for (currow = 1; currow < HT16K33_MAX_ROW; ++currow) | 
|  | { | 
|  | priv->row = currow; | 
|  | for (curcol = 0; curcol < HT16K33_MAX_COL; ++curcol) | 
|  | { | 
|  | priv->col = curcol; | 
|  | data[curcol] = lcd_getdata(priv); | 
|  | } | 
|  |  | 
|  | priv->col = 0; | 
|  | priv->row = currow - 1; | 
|  | lcd_set_curpos(priv); | 
|  | for (curcol = 0; curcol < HT16K33_MAX_COL; ++curcol) | 
|  | { | 
|  | lcd_putdata(priv, data[curcol]); | 
|  | } | 
|  | } | 
|  |  | 
|  | ht16k33_clear_display(priv); | 
|  |  | 
|  | kmm_free(data); | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ht16k33_clear_display | 
|  | * | 
|  | * Description: | 
|  | *  Clear the display writing space (' ') to all positions | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static void ht16k33_clear_display(FAR struct ht16k33_dev_s *priv) | 
|  | { | 
|  | int curcol; | 
|  |  | 
|  | priv->col = 0; | 
|  | priv->row = HT16K33_MAX_ROW - 1; | 
|  | lcd_set_curpos(priv); | 
|  | for (curcol = 0; curcol < HT16K33_MAX_COL; ++curcol) | 
|  | { | 
|  | lcd_putdata(priv, ' '); | 
|  | } | 
|  |  | 
|  | priv->col = 0; | 
|  | priv->row = HT16K33_MAX_ROW - 1; | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: lcd_codec_action | 
|  | * | 
|  | * Description: | 
|  | *  Perform an 'action' as per the Segment LCD codec. | 
|  | * | 
|  | * Input Parameters: | 
|  | *  priv - device instance | 
|  | *  code - SLCD code action code | 
|  | *  count - count param for those actions that take it | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static void lcd_codec_action(FAR struct ht16k33_dev_s *priv, | 
|  | enum slcdcode_e code, uint8_t count) | 
|  | { | 
|  | switch (code) | 
|  | { | 
|  | /* Erasure */ | 
|  |  | 
|  | case SLCDCODE_BACKDEL:         /* Backspace (backward delete) | 
|  | * N characters | 
|  | */ | 
|  | { | 
|  | if (count <= 0)            /* we need to delete more 0 positions */ | 
|  | { | 
|  | break; | 
|  | } | 
|  | else | 
|  | { | 
|  | if (count > priv->col) /* saturate to preceding columns | 
|  | * available | 
|  | */ | 
|  | { | 
|  | count = priv->col; | 
|  | } | 
|  |  | 
|  | priv->col = priv->col - count; | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  |  | 
|  | /* ... and conscientiously fall through to next case ... */ | 
|  | } | 
|  |  | 
|  | case SLCDCODE_FWDDEL:          /* Delete (forward delete) N characters | 
|  | * moving text | 
|  | */ | 
|  | { | 
|  | if (count <= 0)            /* we need to delete more 0 positions */ | 
|  | { | 
|  | break; | 
|  | } | 
|  | else | 
|  | { | 
|  | uint8_t start; | 
|  | uint8_t end; | 
|  | uint8_t i; | 
|  | uint8_t data; | 
|  |  | 
|  | start = priv->col + count; | 
|  |  | 
|  | if (start >= HT16K33_MAX_COL)    /* nothing left */ | 
|  | { | 
|  | break; | 
|  | } | 
|  |  | 
|  | end = start + count; | 
|  | if (end > HT16K33_MAX_COL)      /* saturate */ | 
|  | { | 
|  | end = HT16K33_MAX_COL; | 
|  | } | 
|  |  | 
|  | for (i = priv->col; i < end; ++start, ++i) /* like memmove */ | 
|  | { | 
|  | priv->col = start; | 
|  | lcd_set_curpos(priv); | 
|  | data = lcd_getdata(priv); | 
|  | priv->col = i; | 
|  | lcd_set_curpos(priv); | 
|  | lcd_putdata(priv, data); | 
|  | } | 
|  |  | 
|  | for (; i < HT16K33_MAX_COL; ++i) /* much like memset */ | 
|  | { | 
|  | lcd_putdata(priv, ' '); | 
|  | } | 
|  |  | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_ERASE:           /* Erase N characters from the cursor | 
|  | * position | 
|  | */ | 
|  | if (count > 0) | 
|  | { | 
|  | uint8_t end; | 
|  | uint8_t i; | 
|  |  | 
|  | end = priv->col + count; | 
|  | if (end > HT16K33_MAX_COL) | 
|  | { | 
|  | end = HT16K33_MAX_COL; | 
|  | } | 
|  |  | 
|  | for (i = priv->col; i < end; ++i) | 
|  | { | 
|  | lcd_putdata(priv, ' '); | 
|  | } | 
|  |  | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_CLEAR:           /* Home the cursor and erase the entire | 
|  | * display | 
|  | */ | 
|  | { | 
|  | /* ht16k33_write_cmd(priv, HT16K33_CLEAR_DISPLAY); */ | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_ERASEEOL:        /* Erase from the cursor position to | 
|  | * the end of line | 
|  | */ | 
|  | { | 
|  | uint8_t i; | 
|  |  | 
|  | for (i = priv->col; i < HT16K33_MAX_COL; ++i) | 
|  | { | 
|  | lcd_putdata(priv, ' '); | 
|  | } | 
|  |  | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | break; | 
|  |  | 
|  | /* Cursor movement */ | 
|  |  | 
|  | case SLCDCODE_LEFT:            /* Cursor left by N characters */ | 
|  | { | 
|  | if (count > priv->col) | 
|  | { | 
|  | priv->col = 0; | 
|  | } | 
|  | else | 
|  | { | 
|  | priv->col -= count; | 
|  | } | 
|  |  | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_RIGHT:           /* Cursor right by N characters */ | 
|  | { | 
|  | priv->col += count; | 
|  | if (priv->col >= HT16K33_MAX_COL) | 
|  | { | 
|  | priv->col = HT16K33_MAX_COL - 1; | 
|  | } | 
|  |  | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_UP:              /* Cursor up by N lines */ | 
|  | { | 
|  | if (count > priv->row) | 
|  | { | 
|  | priv->row = 0; | 
|  | } | 
|  | else | 
|  | { | 
|  | priv->row -= count; | 
|  | } | 
|  |  | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_DOWN:            /* Cursor down by N lines */ | 
|  | { | 
|  | priv->row += count; | 
|  | if (priv->row >= HT16K33_MAX_ROW) | 
|  | { | 
|  | priv->row = HT16K33_MAX_ROW - 1; | 
|  | } | 
|  |  | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_HOME:            /* Cursor home */ | 
|  | { | 
|  | priv->col = 0; | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_END:             /* Cursor end */ | 
|  | { | 
|  | priv->col = HT16K33_MAX_COL - 1; | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_PAGEUP:          /* Cursor up by N pages */ | 
|  | case SLCDCODE_PAGEDOWN:        /* Cursor down by N pages */ | 
|  | break;                       /* Not supportable on this SLCD */ | 
|  |  | 
|  | /* Blinking */ | 
|  |  | 
|  | case SLCDCODE_BLINKSTART:      /* Start blinking with current cursor | 
|  | * position | 
|  | */ | 
|  | ht16k33_write_cmd(priv, 0, HT16K33_DISPLAY_SETUP | | 
|  | DISPLAY_SETUP_BLINK_2HZ); | 
|  | break; | 
|  |  | 
|  | case SLCDCODE_BLINKEND:        /* End blinking after the current cursor | 
|  | * position | 
|  | */ | 
|  | case SLCDCODE_BLINKOFF:        /* Turn blinking off */ | 
|  | ht16k33_write_cmd(priv, 0, HT16K33_DISPLAY_SETUP | | 
|  | DISPLAY_SETUP_BLINK_OFF); | 
|  | break;                       /* Not implemented */ | 
|  |  | 
|  | /* These are actually unreportable errors */ | 
|  |  | 
|  | default: | 
|  | case SLCDCODE_NORMAL:          /* Not a special keycode */ | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: lcd_init | 
|  | * | 
|  | * Description: | 
|  | *  perform the initialization sequence to get the LCD into a known state. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static void lcd_init(FAR struct ht16k33_dev_s *priv) | 
|  | { | 
|  | uint8_t data; | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < CONFIG_LCD_HT16K33_NUMBER_MODULES; i++) | 
|  | { | 
|  | /* Initialize the Display: Turn ON Oscillator */ | 
|  |  | 
|  | data = HT16K33_SYSTEM_SETUP | SYSTEM_SETUP_OSC_ON; | 
|  |  | 
|  | ht16k33_write_cmd(priv, i, data); | 
|  |  | 
|  | /* Clear display */ | 
|  |  | 
|  | ht16k33_clear_display(priv); | 
|  |  | 
|  | /* Display ON */ | 
|  |  | 
|  | data = HT16K33_DISPLAY_SETUP | DISPLAY_SETUP_DISP_ON; | 
|  |  | 
|  | ht16k33_write_cmd(priv, i, data); | 
|  | } | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: lcd_curpos_to_fpos | 
|  | * | 
|  | * Description: | 
|  | *   Convert a screen cursor pos (row,col) to a file logical offset.  This | 
|  | *   includes 'synthesized' line feeds at the end of screen lines. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static void lcd_curpos_to_fpos(FAR struct ht16k33_dev_s *priv, | 
|  | uint8_t row, uint8_t col, FAR off_t *fpos) | 
|  | { | 
|  | /* the logical file position is the linear position plus any synthetic LF */ | 
|  |  | 
|  | *fpos = (row * HT16K33_MAX_COL) + col + row; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ht16k33_read | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static ssize_t ht16k33_read(FAR struct file *filep, FAR char *buffer, | 
|  | size_t buflen) | 
|  | { | 
|  | return -ENOSYS; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ht16k33_write | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static ssize_t ht16k33_write(FAR struct file *filep, FAR const char *buffer, | 
|  | size_t buflen) | 
|  | { | 
|  | FAR struct inode *inode = filep->f_inode; | 
|  | FAR struct ht16k33_dev_s *priv = inode->i_private; | 
|  | struct lib_meminstream_s instream; | 
|  | struct slcdstate_s state; | 
|  | enum slcdret_e result; | 
|  | uint8_t ch; | 
|  | uint8_t count; | 
|  |  | 
|  | nxmutex_lock(&priv->lock); | 
|  |  | 
|  | /* Initialize the stream for use with the SLCD CODEC */ | 
|  |  | 
|  | lib_meminstream(&instream, buffer, buflen); | 
|  |  | 
|  | /* Now decode and process every byte in the input buffer */ | 
|  |  | 
|  | memset(&state, 0, sizeof(struct slcdstate_s)); | 
|  | while ((result = slcd_decode(&instream.common, | 
|  | &state, &ch, &count)) != SLCDRET_EOF) | 
|  | { | 
|  | /* Is there some pending scroll? */ | 
|  |  | 
|  | if (priv->pendscroll) | 
|  | { | 
|  | lcd_scroll_up(priv); | 
|  | priv->pendscroll = false; | 
|  | } | 
|  |  | 
|  | if (result == SLCDRET_CHAR)          /* A normal character was returned */ | 
|  | { | 
|  | /* Check for ASCII control characters */ | 
|  |  | 
|  | if (ch == ASCII_TAB) | 
|  | { | 
|  | /* TODO: define what TAB should do */ | 
|  | } | 
|  | else if (ch == ASCII_VT) | 
|  | { | 
|  | /* Turn the backlight on */ | 
|  |  | 
|  | /* TODO: lcd_backlight(priv, true); */ | 
|  | } | 
|  | else if (ch == ASCII_FF) | 
|  | { | 
|  | /* Turn the backlight off */ | 
|  |  | 
|  | /* TODO: lcd_backlight(priv, false); */ | 
|  | } | 
|  | else if (ch == ASCII_CR) | 
|  | { | 
|  | /* Perform a Home */ | 
|  |  | 
|  | priv->col = 0; | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | else if (ch == ASCII_SO) | 
|  | { | 
|  | /* TODO: We don't have cursor */ | 
|  | } | 
|  | else if (ch == ASCII_SI) | 
|  | { | 
|  | /* Perform the re-initialize */ | 
|  |  | 
|  | lcd_init(priv); | 
|  | priv->row = 0; | 
|  | priv->col = 0; | 
|  | } | 
|  | else if (ch == ASCII_LF) | 
|  | { | 
|  | /* unixian line term; go to start of next line */ | 
|  |  | 
|  | priv->row += 1; | 
|  | if (priv->row >= HT16K33_MAX_ROW) | 
|  | { | 
|  | priv->pendscroll = true; | 
|  | priv->row = HT16K33_MAX_ROW - 1; | 
|  | } | 
|  |  | 
|  | priv->col = 0; | 
|  | lcd_set_curpos(priv); | 
|  | } | 
|  | else if (ch == ASCII_BS) | 
|  | { | 
|  | /* Perform the backward deletion */ | 
|  |  | 
|  | lcd_codec_action(priv, SLCDCODE_BACKDEL, 1); | 
|  | } | 
|  | else if (ch == ASCII_DEL) | 
|  | { | 
|  | /* Perform the forward deletion */ | 
|  |  | 
|  | lcd_codec_action(priv, SLCDCODE_FWDDEL, 1); | 
|  | } | 
|  | else | 
|  | { | 
|  | /* Just print it! */ | 
|  |  | 
|  | lcd_putdata(priv, ch); | 
|  | } | 
|  | } | 
|  | else /* (result == SLCDRET_SPEC) */  /* A special SLCD action was returned */ | 
|  | { | 
|  | lcd_codec_action(priv, (enum slcdcode_e)ch, count); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Wherever we wound up, update our logical file pos to reflect it */ | 
|  |  | 
|  | lcd_curpos_to_fpos(priv, priv->row, priv->col, &filep->f_pos); | 
|  |  | 
|  | nxmutex_unlock(&priv->lock); | 
|  | return buflen; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ht16k33_seek | 
|  | * | 
|  | * Description: | 
|  | *   Seek the logical file pointer to the specified position.  This is | 
|  | *   probably not very interesting except possibly for (SEEK_SET, 0) to | 
|  | *   rewind the pointer for a subsequent read(). | 
|  | *   The file pointer is logical, and includes synthesized LF chars at the | 
|  | *   end of the display lines. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static off_t ht16k33_seek(FAR struct file *filep, off_t offset, int whence) | 
|  | { | 
|  | FAR struct inode *inode = filep->f_inode; | 
|  | FAR struct ht16k33_dev_s *priv = | 
|  | inode->i_private; | 
|  | off_t maxpos; | 
|  | off_t pos; | 
|  |  | 
|  | nxmutex_lock(&priv->lock); | 
|  |  | 
|  | maxpos = HT16K33_MAX_ROW * HT16K33_MAX_COL + (HT16K33_MAX_ROW - 1); | 
|  | pos    = filep->f_pos; | 
|  |  | 
|  | switch (whence) | 
|  | { | 
|  | case SEEK_CUR: | 
|  | pos += offset; | 
|  | if (pos > maxpos) | 
|  | { | 
|  | pos = maxpos; | 
|  | } | 
|  | else if (pos < 0) | 
|  | { | 
|  | pos = 0; | 
|  | } | 
|  |  | 
|  | filep->f_pos = pos; | 
|  | break; | 
|  |  | 
|  | case SEEK_SET: | 
|  | pos = offset; | 
|  | if (pos > maxpos) | 
|  | { | 
|  | pos = maxpos; | 
|  | } | 
|  | else if (pos < 0) | 
|  | { | 
|  | pos = 0; | 
|  | } | 
|  |  | 
|  | filep->f_pos = pos; | 
|  | break; | 
|  |  | 
|  | case SEEK_END: | 
|  | pos = maxpos + offset; | 
|  | if (pos > maxpos) | 
|  | { | 
|  | pos = maxpos; | 
|  | } | 
|  | else if (pos < 0) | 
|  | { | 
|  | pos = 0; | 
|  | } | 
|  |  | 
|  | filep->f_pos = pos; | 
|  | break; | 
|  |  | 
|  | default: | 
|  |  | 
|  | /* Return EINVAL if the whence argument is invalid */ | 
|  |  | 
|  | pos = (off_t)-EINVAL; | 
|  | break; | 
|  | } | 
|  |  | 
|  | nxmutex_unlock(&priv->lock); | 
|  | return pos; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ht16k33_ioctl | 
|  | * | 
|  | * Description: | 
|  | *   Perform device operations that are outside the standard I/O model. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | static int ht16k33_ioctl(FAR struct file *filep, int cmd, | 
|  | unsigned long arg) | 
|  | { | 
|  | switch (cmd) | 
|  | { | 
|  | case SLCDIOC_GETATTRIBUTES: /* Get the attributes of the SLCD */ | 
|  | { | 
|  | FAR struct slcd_attributes_s *attr = | 
|  | (FAR struct slcd_attributes_s *)((uintptr_t)arg); | 
|  |  | 
|  | lcdinfo("SLCDIOC_GETATTRIBUTES:\n"); | 
|  |  | 
|  | if (!attr) | 
|  | { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | attr->nrows         = HT16K33_MAX_ROW; | 
|  | attr->ncolumns      = HT16K33_MAX_COL; | 
|  | attr->nbars         = 0; | 
|  | attr->maxcontrast   = 0; | 
|  | attr->maxbrightness = 16;  /* 'brightness' for us is the backlight */ | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDIOC_CURPOS:        /* Get the SLCD cursor position */ | 
|  | { | 
|  | FAR struct inode *inode = filep->f_inode; | 
|  | FAR struct ht16k33_dev_s *priv = | 
|  | inode->i_private; | 
|  | FAR struct slcd_curpos_s *attr = | 
|  | (FAR struct slcd_curpos_s *)((uintptr_t)arg); | 
|  |  | 
|  | attr->row    = priv->row; | 
|  | attr->column = priv->col; | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDIOC_GETBRIGHTNESS: /* Get the current brightness setting */ | 
|  | { | 
|  | FAR struct inode *inode = filep->f_inode; | 
|  | FAR struct ht16k33_dev_s *priv = | 
|  | inode->i_private; | 
|  |  | 
|  | nxmutex_lock(&priv->lock); | 
|  | *(FAR int *)((uintptr_t)arg) = 1; /* Hardcoded */ | 
|  | nxmutex_unlock(&priv->lock); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDIOC_SETBRIGHTNESS: /* Set the brightness to a new value */ | 
|  | { | 
|  | FAR struct inode *inode = filep->f_inode; | 
|  | FAR struct ht16k33_dev_s *priv = | 
|  | inode->i_private; | 
|  |  | 
|  | nxmutex_lock(&priv->lock); | 
|  | ht16k33_setcontrast(priv, 0, (uint8_t)arg); | 
|  | nxmutex_unlock(&priv->lock); | 
|  | } | 
|  | break; | 
|  |  | 
|  | case SLCDIOC_SETBAR:        /* Set bars on a bar display */ | 
|  | case SLCDIOC_GETCONTRAST:   /* Get the current contrast setting */ | 
|  | case SLCDIOC_SETCONTRAST:   /* Set the contrast to a new value */ | 
|  | default: | 
|  | return -ENOTTY; | 
|  | } | 
|  |  | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Public Functions | 
|  | ****************************************************************************/ | 
|  |  | 
|  | /**************************************************************************** | 
|  | * Name: ht16k33_register | 
|  | * | 
|  | * Description: | 
|  | *   Register the HT16K33 character device as 'devpath' | 
|  | * | 
|  | * Input Parameters: | 
|  | *   devno   - The device number to register. E.g., "/dev/slcd0" | 
|  | *   i2c     - An instance of the I2C interface to use to communicate with | 
|  | *             HT16K33 | 
|  | * | 
|  | * Returned Value: | 
|  | *   Zero (OK) on success; a negated errno value on failure. | 
|  | * | 
|  | ****************************************************************************/ | 
|  |  | 
|  | int ht16k33_register(int devno, FAR struct i2c_master_s *i2c) | 
|  | { | 
|  | FAR struct ht16k33_dev_s *priv; | 
|  | char devname[DEVNAME_FMTLEN]; | 
|  | int ret; | 
|  |  | 
|  | /* Initialize the HT16K33 device structure */ | 
|  |  | 
|  | priv = (FAR struct ht16k33_dev_s *) | 
|  | kmm_malloc(sizeof(struct ht16k33_dev_s)); | 
|  | if (!priv) | 
|  | { | 
|  | snerr("ERROR: Failed to allocate instance\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | /* Setup priv with initial values */ | 
|  |  | 
|  | priv->i2c        = i2c; | 
|  | priv->col        = 0; | 
|  | priv->row        = 0; | 
|  | priv->pendscroll = false; | 
|  |  | 
|  | nxmutex_init(&priv->lock); | 
|  |  | 
|  | /* Initialize the display */ | 
|  |  | 
|  | lcd_init(priv); | 
|  |  | 
|  | /* Create the character device name */ | 
|  |  | 
|  | snprintf(devname, sizeof(devname), DEVNAME_FMT, devno); | 
|  |  | 
|  | /* Register the driver */ | 
|  |  | 
|  | ret = register_driver(devname, &g_ht16k33fops, 0666, priv); | 
|  | if (ret < 0) | 
|  | { | 
|  | snerr("ERROR: Failed to register driver: %d\n", ret); | 
|  | nxmutex_destroy(&priv->lock); | 
|  | kmm_free(priv); | 
|  | } | 
|  |  | 
|  | return ret; | 
|  | } | 
|  | #endif /* CONFIG_SPI && CONFIG_HT16K33 */ |