devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] TTY Driver for Newhaven LCD module
@ 2015-03-17 20:36 Alan Tull
  2015-03-17 20:36 ` [PATCH 1/2] newhaven lcd: device tree bindings documentation Alan Tull
  2015-03-17 20:36 ` [PATCH 2/2] add newhaven lcd tty driver on i2c Alan Tull
  0 siblings, 2 replies; 6+ messages in thread
From: Alan Tull @ 2015-03-17 20:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Jiri Slaby, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Alan Tull, devicetree, linux-kernel, delicious.quinoa,
	dinguyen, yvanderv

Supports a 2 line by 16 character LCD module over I2C.

Alan Tull (2):
  newhaven lcd: device tree bindings documentation
  add newhaven lcd tty driver on i2c

 .../devicetree/bindings/tty/newhaven_lcd.txt       |   21 +
 drivers/tty/Kconfig                                |    5 +
 drivers/tty/Makefile                               |    1 +
 drivers/tty/newhaven_lcd.c                         |  733 ++++++++++++++++++++
 include/linux/platform_data/newhaven_lcd.h         |   25 +
 5 files changed, 785 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/tty/newhaven_lcd.txt
 create mode 100644 drivers/tty/newhaven_lcd.c
 create mode 100644 include/linux/platform_data/newhaven_lcd.h

-- 
1.7.9.5

^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH 1/2] newhaven lcd: device tree bindings documentation
  2015-03-17 20:36 [PATCH 0/2] TTY Driver for Newhaven LCD module Alan Tull
@ 2015-03-17 20:36 ` Alan Tull
  2015-03-17 20:36 ` [PATCH 2/2] add newhaven lcd tty driver on i2c Alan Tull
  1 sibling, 0 replies; 6+ messages in thread
From: Alan Tull @ 2015-03-17 20:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Jiri Slaby, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Alan Tull, devicetree, linux-kernel, delicious.quinoa,
	dinguyen, yvanderv

Add documention for the newhaven lcd device tree bindings.

Signed-off-by: Alan Tull <atull@opensource.altera.com>
---
 .../devicetree/bindings/tty/newhaven_lcd.txt       |   21 ++++++++++++++++++++
 1 file changed, 21 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/tty/newhaven_lcd.txt

diff --git a/Documentation/devicetree/bindings/tty/newhaven_lcd.txt b/Documentation/devicetree/bindings/tty/newhaven_lcd.txt
new file mode 100644
index 0000000..14b48fc
--- /dev/null
+++ b/Documentation/devicetree/bindings/tty/newhaven_lcd.txt
@@ -0,0 +1,21 @@
+* TTY on a Newhaven NHD‐0216K3Z‐NSW‐BBW LCD connected to I2C
+
+Required properties:
+- compatible: Should be "newhaven,nhd-0216k3z-nsw-bbw"
+- reg: i2c address
+- height: should be 2 lines
+- width: should be 16 characters
+- brightness: backlight brightness. Range is 1 to 8, where
+              1=OFF and 8=maximum brightness.
+
+Example:
+
+&i2c0 {
+	lcd: lcd@28 {
+		compatible = "newhaven,nhd-0216k3z-nsw-bbw";
+		reg = <0x28>;
+		height = <2>;
+		width = <16>;
+		brightness = <8>;
+	};
+
-- 
1.7.9.5

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH 2/2] add newhaven lcd tty driver on i2c
  2015-03-17 20:36 [PATCH 0/2] TTY Driver for Newhaven LCD module Alan Tull
  2015-03-17 20:36 ` [PATCH 1/2] newhaven lcd: device tree bindings documentation Alan Tull
@ 2015-03-17 20:36 ` Alan Tull
  2015-03-17 21:16   ` Greg Kroah-Hartman
  1 sibling, 1 reply; 6+ messages in thread
From: Alan Tull @ 2015-03-17 20:36 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Jiri Slaby, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Alan Tull, devicetree, linux-kernel, delicious.quinoa,
	dinguyen, yvanderv

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@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

^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/2] add newhaven lcd tty driver on i2c
  2015-03-17 20:36 ` [PATCH 2/2] add newhaven lcd tty driver on i2c Alan Tull
@ 2015-03-17 21:16   ` Greg Kroah-Hartman
  2015-03-17 21:39     ` atull
  2015-03-20 20:46     ` atull
  0 siblings, 2 replies; 6+ messages in thread
From: Greg Kroah-Hartman @ 2015-03-17 21:16 UTC (permalink / raw)
  To: Alan Tull
  Cc: Jiri Slaby, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, devicetree, linux-kernel, delicious.quinoa, dinguyen,
	yvanderv

On Tue, Mar 17, 2015 at 03:36:47PM -0500, Alan Tull wrote:
> 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@opensource.altera.com>

This should use the new fb_tft api instead of trying to look like a tty
device.  That framework is merged in the staging-next branch of the
staging.git kernel tree and will show up in 4.1-rc1.  Please port this
driver to that api and submit a patch for me to take for the staging
tree so that we don't have one-off devices like this.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/2] add newhaven lcd tty driver on i2c
  2015-03-17 21:16   ` Greg Kroah-Hartman
@ 2015-03-17 21:39     ` atull
  2015-03-20 20:46     ` atull
  1 sibling, 0 replies; 6+ messages in thread
From: atull @ 2015-03-17 21:39 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Jiri Slaby, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, devicetree, linux-kernel, delicious.quinoa, dinguyen,
	yvanderv

[-- Attachment #1: Type: text/plain, Size: 1661 bytes --]

On Tue, 17 Mar 2015, Greg Kroah-Hartman wrote:

> On Tue, Mar 17, 2015 at 03:36:47PM -0500, Alan Tull wrote:
> > 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@opensource.altera.com>
> 
> This should use the new fb_tft api instead of trying to look like a tty
> device.  That framework is merged in the staging-next branch of the
> staging.git kernel tree and will show up in 4.1-rc1.  Please port this
> driver to that api and submit a patch for me to take for the staging
> tree so that we don't have one-off devices like this.

Cool! Will do.

Thanks!
Alan

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

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH 2/2] add newhaven lcd tty driver on i2c
  2015-03-17 21:16   ` Greg Kroah-Hartman
  2015-03-17 21:39     ` atull
@ 2015-03-20 20:46     ` atull
  1 sibling, 0 replies; 6+ messages in thread
From: atull @ 2015-03-20 20:46 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Jiri Slaby, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, devicetree, linux-kernel, delicious.quinoa, dinguyen,
	yvanderv

[-- Attachment #1: Type: text/plain, Size: 1833 bytes --]

On Tue, 17 Mar 2015, Greg Kroah-Hartman wrote:

> On Tue, Mar 17, 2015 at 03:36:47PM -0500, Alan Tull wrote:
> > 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@opensource.altera.com>
> 
> This should use the new fb_tft api instead of trying to look like a tty
> device.  That framework is merged in the staging-next branch of the
> staging.git kernel tree and will show up in 4.1-rc1.  Please port this
> driver to that api and submit a patch for me to take for the staging
> tree so that we don't have one-off devices like this.
> 

Hi Greg,

This isn't framebuffer type of device.  It's two rows of 16 characters
and you can write ascii characters to it.  If there's another framework
more suited to this, that would be great.

Alan

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

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2015-03-20 20:46 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-03-17 20:36 [PATCH 0/2] TTY Driver for Newhaven LCD module Alan Tull
2015-03-17 20:36 ` [PATCH 1/2] newhaven lcd: device tree bindings documentation Alan Tull
2015-03-17 20:36 ` [PATCH 2/2] add newhaven lcd tty driver on i2c Alan Tull
2015-03-17 21:16   ` Greg Kroah-Hartman
2015-03-17 21:39     ` atull
2015-03-20 20:46     ` atull

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).