Supports the Newhaven NHD‐0216K3Z‐NSW‐BBW 2x16 LCD module as i2c slave.
Devices will show up as /dev/ttyLCD0, etc.

 * Backspace is supported to the beginning of the current line.
    * i.e. printf '\b' > /dev/ttyLCD0

 * ESC [ 2 J
    * erase whole display and reset cursor to home.
    * i.e. printf '\e[2J' > /dev/ttyLCD0

 * ESC [ 2 K
    * erase current line and set cursor to beginning of line.
    * i.e. printf '\e[2K' > /dev/ttyLCD0

 * CR and LF are supported.

 * Vertical scroll when cursor is on bottom line and receive end of line.

Default brightness can be set from the device tree/plat data.

Brightness can be set from a sysfs file, for example:
 * echo 6 > /sys/devices/soc.0/ffc04000.i2c/i2c-0/0-0028/brightness

Signed-off-by: Alan Tull <at...@opensource.altera.com>
---
 drivers/tty/Kconfig                        |    5 +
 drivers/tty/Makefile                       |    1 +
 drivers/tty/newhaven_lcd.c                 |  733 ++++++++++++++++++++++++++++
 include/linux/platform_data/newhaven_lcd.h |   25 +
 4 files changed, 764 insertions(+)
 create mode 100644 drivers/tty/newhaven_lcd.c
 create mode 100644 include/linux/platform_data/newhaven_lcd.h

diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index b24aa01..c392405 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -419,4 +419,9 @@ config DA_CONSOLE
        help
          This enables a console on a Dash channel.
 
+config NEWHAVEN_LCD
+       tristate "NEWHAVEN LCD"
+       help
+         Add support for a TTY device on a Newhaven I2C LCD device.
+
 endif # TTY
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 58ad1c0..f6a3d56 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -29,5 +29,6 @@ obj-$(CONFIG_SYNCLINK)                += synclink.o
 obj-$(CONFIG_PPC_EPAPR_HV_BYTECHAN) += ehv_bytechan.o
 obj-$(CONFIG_GOLDFISH_TTY)     += goldfish.o
 obj-$(CONFIG_DA_TTY)           += metag_da.o
+obj-$(CONFIG_NEWHAVEN_LCD)     += newhaven_lcd.o
 
 obj-y += ipwireless/
diff --git a/drivers/tty/newhaven_lcd.c b/drivers/tty/newhaven_lcd.c
new file mode 100644
index 0000000..d79ee47
--- /dev/null
+++ b/drivers/tty/newhaven_lcd.c
@@ -0,0 +1,733 @@
+/*
+ * TTY on a LCD connected to I2C
+ * Supports Newhaven NHD-0216K3Z-NSW-BBW Serial LCD Module
+ *
+ * Copyright (C) 2013-2015 Altera Corporation.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/idr.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_data/newhaven_lcd.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+
+#define DRV_NAME "lcd-comm"
+#define DEV_NAME "ttyLCD"
+#define MAX_NEWHAVEN_LCD_COUNT 256
+
+#define LCD_COMMAND             0xfe
+#define LCD_DISPLAY_ON          0x41
+#define LCD_DISPLAY_OFF         0x42
+#define LCD_SET_CURSOR          0x45
+#define LCD_BACKSPACE           0x4e
+#define LCD_CLEAR_SCREEN        0x51
+#define LCD_BRIGHTNESS          0x53
+#define LCD_CUSTOM_CHAR         0x54
+#define LCD_BYTES_PER_FONT      8
+#define LCD_BYTES_PER_FONT_CMD  (LCD_BYTES_PER_FONT + 3)
+
+#define LCD_BRIGHTNESS_MIN     1
+#define LCD_BRIGHTNESS_MAX     8
+
+#define ASCII_BS                0x08
+#define ASCII_LF                0x0a
+#define ASCII_CR                0x0d
+#define ASCII_ESC               0x1b
+#define ASCII_SPACE             0x20
+#define ASCII_BACKSLASH         0x5c
+#define ASCII_TILDE             0x7e
+
+/* Valid displayable character in LCD panel's font table */
+#define valid_font(x) (0x20 <= (x) && (x) <= 0x7f)
+
+/*
+ * The display module displays a right arrow instead of tilde for
+ * ascii 0x7e. Also, it displays a Japanese character instead of a
+ * backslash character for ascii 0x5c. Work around these by loading
+ * custom characters into the display module's cg ram.
+ */
+struct custom_font {
+       char font[LCD_BYTES_PER_FONT];
+       char ascii;
+};
+
+#define CUSTOM_BACKSLASH        0x00
+#define CUSTOM_TILDE            0x01
+
+struct custom_font custom_fonts[] = {
+       [CUSTOM_BACKSLASH] = {
+               { 0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, },
+               ASCII_BACKSLASH,
+       },
+       [CUSTOM_TILDE] = {
+               { 0x00, 0x00, 0x00, 0x08, 0x15, 0x02, 0x00, 0x00, },
+               ASCII_TILDE,
+       },
+};
+
+struct lcd {
+       struct device *dev;
+       struct i2c_client *client;
+       struct tty_port port;
+       unsigned int width;
+       unsigned int height;
+       unsigned int brightness;
+       char *buffer;
+       unsigned int top_line;
+       unsigned int cursor_line;
+       unsigned int cursor_col;
+       unsigned int index;
+       struct list_head next;
+};
+
+static LIST_HEAD(lcd_structs);
+static DEFINE_SPINLOCK(lcd_structs_lock);
+static DEFINE_IDA(lcd_ida);
+
+static struct lcd *lcd_get_by_index(int index)
+{
+       struct lcd *lcd_data;
+
+       spin_lock(&lcd_structs_lock);
+
+       list_for_each_entry(lcd_data, &lcd_structs, next) {
+               if (lcd_data->index == index) {
+                       tty_port_get(&lcd_data->port);
+                       spin_unlock(&lcd_structs_lock);
+                       return lcd_data;
+               }
+       }
+
+       spin_unlock(&lcd_structs_lock);
+       return NULL;
+}
+
+/*
+ * The Newhaven NHD-0216K3Z-NSW-BBW runs at max 100KHz I2C rate but also
+ * requires some execution time between commands.  Execution time for each
+ * command is listed in the datasheet (100uSec to 4mSec).  Even adding
+ * sleeps between commands isn't sufficient for reliable operation.  Running
+ * the I2C slower, such as at 50KHz is better.
+ */
+static void lcd_i2c_master_send(const struct i2c_client *client,
+                               const char *buf, int count, int delay_ms)
+{
+       int ret;
+
+       ret = i2c_master_send(client, buf, count);
+       if (ret != sizeof(buf))
+               dev_dbg(&client->dev, "i2c_master_send returns %d\n", ret);
+       if (delay_ms)
+               msleep(delay_ms);
+}
+
+static void lcd_cmd_no_params(struct lcd *lcd_data, char cmd, int delay_ms)
+{
+       char buf[2] = {LCD_COMMAND, cmd};
+
+       lcd_i2c_master_send(lcd_data->client, buf, sizeof(buf), delay_ms);
+}
+
+static void lcd_cmd_one_param(struct lcd *lcd_data, char cmd, char param,
+                             int delay_ms)
+{
+       char buf[3] = {LCD_COMMAND, cmd, param};
+
+       lcd_i2c_master_send(lcd_data->client, buf, sizeof(buf), delay_ms);
+}
+
+static void lcd_cmd_backlight_brightness(struct lcd *lcd_data)
+{
+       lcd_cmd_one_param(lcd_data, LCD_BRIGHTNESS, lcd_data->brightness, 1);
+}
+
+static void lcd_cmd_display_on(struct lcd *lcd_data)
+{
+       lcd_cmd_no_params(lcd_data, LCD_DISPLAY_ON, 1);
+}
+
+static void lcd_cmd_display_off(struct lcd *lcd_data)
+{
+       lcd_cmd_no_params(lcd_data, LCD_DISPLAY_OFF, 1);
+}
+
+static void lcd_cmd_clear_screen(struct lcd *lcd_data)
+{
+       lcd_cmd_no_params(lcd_data, LCD_CLEAR_SCREEN, 2);
+}
+
+static void lcd_cmd_backspace(struct lcd *lcd_data)
+{
+       lcd_cmd_no_params(lcd_data, LCD_BACKSPACE, 1);
+}
+
+/*
+ * Note that this has to happen early on or the LCD module will not
+ * process the command.
+ */
+static void lcd_load_custom_fonts(struct lcd *lcd_data)
+{
+       char buf[LCD_BYTES_PER_FONT_CMD];
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(custom_fonts); i++) {
+               buf[0] = LCD_COMMAND;
+               buf[1] = LCD_CUSTOM_CHAR;
+               buf[2] = i;
+               memcpy(buf + 3, &custom_fonts[i].font, LCD_BYTES_PER_FONT);
+               lcd_i2c_master_send(lcd_data->client, buf, sizeof(buf), 1);
+       }
+}
+
+/*
+ * Check to see if the ascii val is a character that we are printing
+ * using a custom font.  If so, return the index of the font.
+ */
+static char lcd_translate_printable_char(char val)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(custom_fonts); i++)
+               if (val == custom_fonts[i].ascii)
+                       return i;
+
+       return val;
+}
+
+/* From NHD-0216K3Z-NSW-BBY Display Module datasheet. */
+#define LCD_CURSOR_LINE_MULTIPLIER 0x40
+
+static void lcd_cmd_set_cursor(struct lcd *lcd_data, unsigned int line,
+                              unsigned int col)
+{
+       unsigned int cursor;
+
+       cursor = col + (LCD_CURSOR_LINE_MULTIPLIER * line);
+
+       lcd_cmd_one_param(lcd_data, LCD_SET_CURSOR, cursor, 1);
+}
+
+/*
+ * Map a line on the lcd display to a line on the buffer.
+ * Note that the top line on the display (line 0) may not be line 0 on the
+ * buffer due to scrolling.
+ */
+static unsigned int lcd_line_to_buf_line(struct lcd *lcd_data,
+                                        unsigned int line)
+{
+       unsigned int buf_line;
+
+       buf_line = line + lcd_data->top_line;
+
+       if (buf_line >= lcd_data->height)
+               buf_line -= lcd_data->height;
+
+       return buf_line;
+}
+
+/* Returns a pointer to the line, column position in the lcd buffer */
+static char *lcd_buf_pointer(struct lcd *lcd_data, unsigned int line,
+                            unsigned int col)
+{
+       unsigned int buf_line;
+       char *buf;
+
+       if ((lcd_data->cursor_line >= lcd_data->height) ||
+           (lcd_data->cursor_col >= lcd_data->width))
+               return lcd_data->buffer;
+
+       buf_line = lcd_line_to_buf_line(lcd_data, line);
+
+       buf = lcd_data->buffer + (buf_line * lcd_data->width) + col;
+
+       return buf;
+}
+
+static void lcd_clear_buffer_line(struct lcd *lcd_data, unsigned int line)
+{
+       char *buf = lcd_buf_pointer(lcd_data, line, 0);
+
+       memset(buf, ASCII_SPACE, lcd_data->width);
+}
+
+static void lcd_clear_buffer(struct lcd *lcd_data)
+{
+       memset(lcd_data->buffer, ASCII_SPACE,
+              lcd_data->width * lcd_data->height);
+       lcd_data->cursor_line = 0;
+       lcd_data->cursor_col = 0;
+       lcd_data->top_line = 0;
+}
+
+static void lcd_reprint_one_line(struct lcd *lcd_data, unsigned int line)
+{
+       char *buf = lcd_buf_pointer(lcd_data, line, 0);
+
+       lcd_cmd_set_cursor(lcd_data, line, 0);
+       lcd_i2c_master_send(lcd_data->client, buf, lcd_data->width, 1);
+}
+
+static void lcd_print_top_n_lines(struct lcd *lcd_data, unsigned int lines)
+{
+       unsigned int disp_line = 0;
+
+       while (disp_line < lines)
+               lcd_reprint_one_line(lcd_data, disp_line++);
+}
+
+static void lcd_add_char_at_cursor(struct lcd *lcd_data, char val)
+{
+       char *buf;
+
+       buf = lcd_buf_pointer(lcd_data, lcd_data->cursor_line,
+                             lcd_data->cursor_col);
+       *buf = val;
+       if (lcd_data->cursor_col < (lcd_data->width - 1))
+               lcd_data->cursor_col++;
+}
+
+static void lcd_crlf(struct lcd *lcd_data)
+{
+       if (lcd_data->cursor_line < (lcd_data->height - 1)) {
+               /* Next line is blank, carriage return to beginning of line. */
+               lcd_data->cursor_line++;
+               if (lcd_data->cursor_line >= lcd_data->height)
+                       lcd_data->cursor_line = 0;
+       } else {
+               /* Display is full.  Scroll up one line. */
+               lcd_data->top_line++;
+               if (lcd_data->top_line >= lcd_data->height)
+                       lcd_data->top_line = 0;
+
+               lcd_cmd_clear_screen(lcd_data);
+               lcd_clear_buffer_line(lcd_data, lcd_data->cursor_line);
+               lcd_print_top_n_lines(lcd_data, lcd_data->height);
+       }
+
+       lcd_cmd_set_cursor(lcd_data, lcd_data->height - 1, 0);
+       lcd_data->cursor_col = 0;
+}
+
+static void lcd_backspace(struct lcd *lcd_data)
+{
+       if (lcd_data->cursor_col > 0) {
+               lcd_cmd_backspace(lcd_data);
+               lcd_data->cursor_col--;
+       }
+}
+
+static void lcd_clear_screen(struct lcd *lcd_data)
+{
+       lcd_clear_buffer(lcd_data);
+       lcd_cmd_clear_screen(lcd_data);
+}
+
+static void lcd_clear_line(struct lcd *lcd_data, unsigned int cursor_line)
+{
+       lcd_clear_buffer_line(lcd_data, cursor_line);
+       lcd_reprint_one_line(lcd_data, cursor_line);
+       lcd_cmd_set_cursor(lcd_data, cursor_line, 0);
+       lcd_data->cursor_col = 0;
+}
+
+static int lcd_write(struct tty_struct *tty, const unsigned char *buf,
+                    int count)
+{
+       struct lcd *lcd_data = tty->driver_data;
+       int buf_i = 0, left;
+       char val;
+
+       if (!lcd_data)
+               return -ENODEV;
+
+       while (buf_i < count) {
+               left = count - buf_i;
+
+               /* process displayable chars */
+               if (valid_font(buf[buf_i])) {
+                       while ((buf_i < count) && valid_font(buf[buf_i])) {
+                               val = lcd_translate_printable_char(buf[buf_i]);
+                               lcd_add_char_at_cursor(lcd_data, val);
+                               buf_i++;
+                       }
+
+                       /* send the line to the display when we get to eol */
+                       lcd_reprint_one_line(lcd_data, lcd_data->cursor_line);
+
+               /*
+                * ECMA-48 CSI sequences (from console_codes man page):
+                *  ESC [ 2 J : erase whole display.
+                *  ESC [ 2 K : erase whole line.
+                */
+               } else if (buf[buf_i] == ASCII_ESC) {
+                       if ((left >= 4) &&
+                           (!strncmp(&buf[buf_i + 1], "[2J", 3))) {
+                               lcd_clear_screen(lcd_data);
+                               buf_i += 4;
+                       } else if ((left >= 4) &&
+                                  (!strncmp(&buf[buf_i + 1], "[2K", 3))) {
+                               lcd_clear_line(lcd_data, lcd_data->cursor_line);
+                               buf_i += 4;
+                       } else {
+                               dev_dbg(lcd_data->dev,
+                                       "Unsupported escape sequence\n");
+                               buf_i++;
+                       }
+
+               } else if ((left >= 2) &&
+                       (buf[buf_i] == ASCII_CR) &&
+                        (buf[buf_i + 1] == ASCII_LF)) {
+                       lcd_crlf(lcd_data);
+                       buf_i += 2;
+
+               } else if ((left >= 1) && (buf[buf_i] == ASCII_CR)) {
+                       lcd_crlf(lcd_data);
+                       buf_i++;
+
+               } else if ((left >= 1) && (buf[buf_i] == ASCII_LF)) {
+                       lcd_crlf(lcd_data);
+                       buf_i++;
+
+               } else if ((left >= 1) && (buf[buf_i] == ASCII_BS)) {
+                       lcd_backspace(lcd_data);
+                       buf_i++;
+
+               } else {
+                       dev_dbg(lcd_data->dev, "Unsupported command 0x%02x\n",
+                               buf[buf_i]);
+                       buf_i++;
+               }
+       }
+
+       return count;
+}
+
+static ssize_t brightness_show(struct device *dev,
+                              struct device_attribute *attr,
+                              char *buf)
+{
+       struct lcd *lcd_data = dev_get_drvdata(dev);
+
+       return sprintf(buf, "%d\n", lcd_data->brightness);
+}
+
+static ssize_t brightness_store(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t count)
+{
+       struct lcd *lcd_data = dev_get_drvdata(dev);
+       unsigned int brightness;
+       int ret;
+
+       ret = kstrtouint(buf, 10, &brightness);
+       if (ret)
+               return ret;
+
+       if ((brightness < LCD_BRIGHTNESS_MIN) ||
+           (brightness > LCD_BRIGHTNESS_MAX)) {
+               dev_err(lcd_data->dev, "out of range (%d to %d)\n",
+                       LCD_BRIGHTNESS_MIN, LCD_BRIGHTNESS_MAX);
+               return -EINVAL;
+       }
+
+       lcd_data->brightness = brightness;
+       lcd_cmd_backlight_brightness(lcd_data);
+
+       return count;
+}
+static DEVICE_ATTR(brightness, S_IRUGO | S_IWUSR,
+                  brightness_show, brightness_store);
+
+static struct attribute *lcd_attrs[] = {
+       &dev_attr_brightness.attr,
+       NULL,
+};
+
+static struct attribute_group lcd_attr_group = {
+       .attrs = lcd_attrs,
+};
+
+static int lcd_install(struct tty_driver *driver, struct tty_struct *tty)
+{
+       struct lcd *lcd_data;
+       int ret;
+
+       lcd_data = lcd_get_by_index(tty->index);
+       if (!lcd_data)
+               return -ENODEV;
+
+       tty->driver_data = lcd_data;
+
+       ret = tty_port_install(&lcd_data->port, driver, tty);
+       if (ret)
+               tty_port_put(&lcd_data->port);
+
+       return ret;
+}
+
+static int lcd_open(struct tty_struct *tty, struct file *filp)
+{
+       struct lcd *lcd_data = tty->driver_data;
+       unsigned long flags;
+
+       tty->driver_data = lcd_data;
+       spin_lock_irqsave(&lcd_data->port.lock, flags);
+       lcd_data->port.count++;
+       spin_unlock_irqrestore(&lcd_data->port.lock, flags);
+       tty_port_tty_set(&lcd_data->port, tty);
+
+       return 0;
+}
+
+static void lcd_close(struct tty_struct *tty, struct file *filp)
+{
+       struct lcd *lcd_data = tty->driver_data;
+       unsigned long flags;
+       bool last;
+
+       spin_lock_irqsave(&lcd_data->port.lock, flags);
+       --lcd_data->port.count;
+       last = (lcd_data->port.count == 0);
+       spin_unlock_irqrestore(&lcd_data->port.lock, flags);
+       if (last)
+               tty_port_tty_set(&lcd_data->port, NULL);
+}
+
+static int lcd_write_room(struct tty_struct *tty)
+{
+       struct lcd *lcd_data = tty->driver_data;
+
+       return lcd_data->height * lcd_data->width;
+}
+
+static const struct tty_operations lcd_ops = {
+       .install         = lcd_install,
+       .open            = lcd_open,
+       .close           = lcd_close,
+       .write           = lcd_write,
+       .write_room      = lcd_write_room,
+};
+
+#ifdef CONFIG_OF
+static struct newhaven_lcd_pdata *lcd_parse_dt(struct device *dev)
+{
+       struct device_node *np = dev->of_node;
+       unsigned int width, height, brightness;
+       struct newhaven_lcd_pdata *pdata;
+
+       if (of_property_read_u32(np, "height", &height) ||
+           of_property_read_u32(np, "width", &width)) {
+               dev_dbg(dev,
+                       "Need to specify lcd width/height in device tree\n");
+               return NULL;
+       }
+
+       if (of_property_read_u32(np, "brightness", &brightness) ||
+           (brightness < LCD_BRIGHTNESS_MIN) ||
+           (brightness > LCD_BRIGHTNESS_MAX))
+               brightness = LCD_BRIGHTNESS_MAX;
+
+       pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+       if (!pdata)
+               return NULL;
+
+       pdata->width = width;
+       pdata->height = height;
+       pdata->brightness = brightness;
+
+       return pdata;
+}
+#else
+static struct newhaven_lcd_pdata *lcd_parse_dt(struct device *dev)
+{
+       return 0;
+}
+#endif
+
+static struct tty_driver *lcd_tty_driver;
+
+static int lcd_probe(struct i2c_client *client,
+                    const struct i2c_device_id *i2c_id)
+{
+       struct newhaven_lcd_pdata *pdata;
+       struct lcd *lcd_data;
+       int id, ret = -ENOMEM;
+
+       pdata = dev_get_platdata(&client->dev);
+       if (!pdata && client->dev.of_node)
+               pdata = lcd_parse_dt(&client->dev);
+
+       if (!pdata) {
+               dev_err(&client->dev, "No platform data found.\n");
+               return -ENODEV;
+       }
+
+       lcd_data = devm_kzalloc(&client->dev, sizeof(*lcd_data), GFP_KERNEL);
+       if (!lcd_data)
+               return -ENOMEM;
+
+       lcd_data->buffer = devm_kzalloc(&client->dev,
+                                       pdata->height * pdata->width,
+                                       GFP_KERNEL);
+       if (!lcd_data->buffer)
+               return -ENOMEM;
+
+       id = ida_simple_get(&lcd_ida, 0, MAX_NEWHAVEN_LCD_COUNT, GFP_KERNEL);
+       if (id < 0)
+               return id;
+       lcd_data->index = id;
+
+       spin_lock(&lcd_structs_lock);
+       list_add_tail(&lcd_data->next, &lcd_structs);
+       spin_unlock(&lcd_structs_lock);
+
+       i2c_set_clientdata(client, lcd_data);
+
+       lcd_data->client  = client;
+       lcd_data->dev     = &client->dev;
+       lcd_data->height  = pdata->height;
+       lcd_data->width   = pdata->width;
+       lcd_data->brightness = pdata->brightness;
+
+       dev_set_drvdata(&client->dev, lcd_data);
+       tty_port_init(&lcd_data->port);
+
+       lcd_clear_buffer(lcd_data);
+       lcd_load_custom_fonts(lcd_data);
+       lcd_cmd_display_on(lcd_data);
+       lcd_cmd_backlight_brightness(lcd_data);
+       lcd_cmd_clear_screen(lcd_data);
+
+       ret = sysfs_create_group(&lcd_data->dev->kobj, &lcd_attr_group);
+       if (ret) {
+               dev_err(lcd_data->dev, "Can't create sysfs attrs for lcd\n");
+               goto err_group;
+       }
+
+       tty_register_device(lcd_tty_driver, lcd_data->index, &client->dev);
+
+       dev_info(&client->dev, "LCD driver initialized\n");
+
+       return 0;
+
+err_group:
+       spin_lock(&lcd_structs_lock);
+       list_del(&lcd_data->next);
+       spin_unlock(&lcd_structs_lock);
+
+       ida_simple_remove(&lcd_ida, lcd_data->index);
+
+       return ret;
+}
+
+static int __exit lcd_remove(struct i2c_client *client)
+{
+       struct lcd *lcd_data = i2c_get_clientdata(client);
+
+       spin_lock(&lcd_structs_lock);
+       list_del(&lcd_data->next);
+       spin_unlock(&lcd_structs_lock);
+
+       ida_simple_remove(&lcd_ida, lcd_data->index);
+
+       lcd_cmd_display_off(lcd_data);
+
+       tty_unregister_device(lcd_tty_driver, lcd_data->index);
+
+       sysfs_remove_group(&lcd_data->dev->kobj, &lcd_attr_group);
+
+       tty_port_put(&lcd_data->port);
+
+       return 0;
+}
+
+static const struct i2c_device_id lcd_id[] = {
+       { DRV_NAME, 0 },
+       { }
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id lcd_of_match[] = {
+       { .compatible = "newhaven,nhd-0216k3z-nsw-bbw", },
+};
+MODULE_DEVICE_TABLE(i2c, lcd_id);
+#endif
+
+static struct i2c_driver lcd_i2c_driver = {
+       .driver = {
+               .name = DRV_NAME,
+               .owner = THIS_MODULE,
+#ifdef CONFIG_OF
+               .of_match_table = lcd_of_match,
+#endif
+       },
+       .probe = lcd_probe,
+       .remove = lcd_remove,
+       .id_table = lcd_id,
+};
+
+static int __init lcd_init(void)
+{
+       int ret;
+
+       lcd_tty_driver = tty_alloc_driver(MAX_NEWHAVEN_LCD_COUNT,
+                                         TTY_DRIVER_DYNAMIC_DEV);
+       if (IS_ERR(lcd_tty_driver))
+               return PTR_ERR(lcd_tty_driver);
+
+       /* initialize the tty_driver structure */
+       lcd_tty_driver->driver_name  = DRV_NAME;
+       lcd_tty_driver->name         = DEV_NAME;
+       lcd_tty_driver->major        = 0;
+       lcd_tty_driver->minor_start  = 0;
+       lcd_tty_driver->type         = TTY_DRIVER_TYPE_SERIAL;
+       lcd_tty_driver->subtype      = SERIAL_TYPE_NORMAL;
+       lcd_tty_driver->init_termios = tty_std_termios;
+       tty_set_operations(lcd_tty_driver, &lcd_ops);
+
+       ret = tty_register_driver(lcd_tty_driver);
+       if (ret)
+               goto lcd_put_tty;
+
+       ret = i2c_add_driver(&lcd_i2c_driver);
+       if (ret)
+               goto lcd_unreg_tty;
+
+       return 0;
+
+lcd_unreg_tty:
+       tty_unregister_driver(lcd_tty_driver);
+
+lcd_put_tty:
+       put_tty_driver(lcd_tty_driver);
+       return ret;
+}
+subsys_initcall(lcd_init);
+
+static void __exit lcd_exit(void)
+{
+       tty_unregister_driver(lcd_tty_driver);
+       put_tty_driver(lcd_tty_driver);
+       i2c_del_driver(&lcd_i2c_driver);
+       ida_destroy(&lcd_ida);
+}
+module_exit(lcd_exit);
+
+MODULE_DESCRIPTION("Newhaven LCD");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/platform_data/newhaven_lcd.h 
b/include/linux/platform_data/newhaven_lcd.h
new file mode 100644
index 0000000..68a6d19
--- /dev/null
+++ b/include/linux/platform_data/newhaven_lcd.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2013-2015 Altera Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __NEWHAVEN_LCD_H
+#define __NEWHAVEN_LCD_H
+
+struct newhaven_lcd_pdata {
+       unsigned int width;
+       unsigned int height;
+       unsigned int brightness;
+};
+
+#endif /* __NEWHAVEN_LCD_H */
-- 
1.7.9.5

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to