lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <1426624607-2832-3-git-send-email-atull@opensource.altera.com>
Date:	Tue, 17 Mar 2015 15:36:47 -0500
From:	Alan Tull <atull@...nsource.altera.com>
To:	Greg Kroah-Hartman <gregkh@...uxfoundation.org>
CC:	Jiri Slaby <jslaby@...e.cz>, Rob Herring <robh+dt@...nel.org>,
	Pawel Moll <pawel.moll@....com>,
	Mark Rutland <mark.rutland@....com>,
	Ian Campbell <ijc+devicetree@...lion.org.uk>,
	Kumar Gala <galak@...eaurora.org>,
	Alan Tull <atull@...nsource.altera.com>,
	<devicetree@...r.kernel.org>, <linux-kernel@...r.kernel.org>,
	<delicious.quinoa@...il.com>, <dinguyen@...nsource.altera.com>,
	<yvanderv@...nsource.altera.com>
Subject: [PATCH 2/2] add newhaven lcd tty driver on i2c

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 <atull@...nsource.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 majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ