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@xxxxxxxxxxxxxxxxxxxxx> --- 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 devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html