From patchwork Mon Dec 17 08:34:15 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lars Poeschel X-Patchwork-Id: 1886341 Return-Path: X-Original-To: patchwork-linux-fbdev@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork2.kernel.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by patchwork2.kernel.org (Postfix) with ESMTP id 6DD99DF215 for ; Mon, 17 Dec 2012 08:34:44 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751410Ab2LQIen (ORCPT ); Mon, 17 Dec 2012 03:34:43 -0500 Received: from [212.90.139.80] ([212.90.139.80]:62047 "EHLO smtp1.goneo.de" rhost-flags-FAIL-FAIL-OK-OK) by vger.kernel.org with ESMTP id S1751397Ab2LQIen (ORCPT ); Mon, 17 Dec 2012 03:34:43 -0500 Received: from smtp1.goneo.de (localhost [127.0.0.1]) by smtp1.goneo.de (Postfix) with ESMTP id 0454B16A7A4; Mon, 17 Dec 2012 09:34:16 +0100 (CET) X-Virus-Scanned: by goneo X-Spam-Flag: NO X-Spam-Score: -2.737 X-Spam-Level: X-Spam-Status: No, score=-2.737 tagged_above=-999 tests=[ALL_TRUSTED=-1, AWL=0.163, BAYES_00=-1.9] autolearn=unavailable Received: from smtp1.goneo.de ([127.0.0.1]) by smtp1.goneo.de (smtp1.goneo.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id DpdrQnOw791Y; Mon, 17 Dec 2012 09:34:05 +0100 (CET) Received: from lem-wkst-02.localnet (p50998852.dip0.t-ipconnect.de [80.153.136.82]) by smtp1.goneo.de (Postfix) with ESMTPSA id 324CB16A7FA; Mon, 17 Dec 2012 09:34:05 +0100 (CET) To: Arnd Bergmann , FlorianSchandinat@gmx.de Subject: [PATCH v2 RESEND] video console: add a driver for lcd2s character display Cc: mathieu.poirier@linaro.org, linux-fbdev@vger.kernel.org, linux-kernel@vger.kernel.org From: Lars Poeschel Date: Mon, 17 Dec 2012 09:34:15 +0100 MIME-Version: 1.0 Message-Id: <201212170934.15312.poeschel@lemonage.de> Sender: linux-fbdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fbdev@vger.kernel.org From: Lars Poeschel This driver allows to use a lcd2s 20x4 character display as a linux console output device. Signed-off-by: Lars Poeschel --- drivers/video/console/Kconfig | 10 ++ drivers/video/console/Makefile | 1 + drivers/video/console/lcd2scon.c | 360 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 drivers/video/console/lcd2scon.c diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig index e2c96d0..44fc3bf 100644 --- a/drivers/video/console/Kconfig +++ b/drivers/video/console/Kconfig @@ -129,6 +129,16 @@ config STI_CONSOLE machines. Say Y here to build support for it into your kernel. The alternative is to use your primary serial port as a console. +config LCD2S_CONSOLE + tristate "lcd2s 20x4 character display over I2C console" + depends on I2C + default n + help + This is a driver that lets you use the lcd2s 20x4 character display + from modtronix engineering as a console output device. The display + is a simple single color character display. You have to connect it + to an I2C bus. + config FONTS bool "Select compiled-in fonts" depends on FRAMEBUFFER_CONSOLE || STI_CONSOLE diff --git a/drivers/video/console/Makefile b/drivers/video/console/Makefile index a862e91..74d5993 100644 --- a/drivers/video/console/Makefile +++ b/drivers/video/console/Makefile @@ -23,6 +23,7 @@ font-objs += $(font-objs-y) obj-$(CONFIG_DUMMY_CONSOLE) += dummycon.o obj-$(CONFIG_SGI_NEWPORT_CONSOLE) += newport_con.o font.o obj-$(CONFIG_STI_CONSOLE) += sticon.o sticore.o font.o +obj-$(CONFIG_LCD2S_CONSOLE) += lcd2scon.o obj-$(CONFIG_VGA_CONSOLE) += vgacon.o obj-$(CONFIG_MDA_CONSOLE) += mdacon.o obj-$(CONFIG_FRAMEBUFFER_CONSOLE) += fbcon.o bitblit.o font.o softcursor.o diff --git a/drivers/video/console/lcd2scon.c b/drivers/video/console/lcd2scon.c new file mode 100644 index 0000000..c139811 --- /dev/null +++ b/drivers/video/console/lcd2scon.c @@ -0,0 +1,360 @@ +/* + * console driver for LCD2S 4x20 character displays connected through i2c. + * + * This is a driver allowing you to use a LCD2S 4x20 from modtronix + * engineering as console output device. + * + * (C) 2012 by Lemonage Software GmbH + * Author: Lars Poeschel + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LCD2S_CMD_CUR_BLINK_OFF 0x10 +#define LCD2S_CMD_CUR_UL_OFF 0x11 +#define LCD2S_CMD_DISPLAY_OFF 0x12 +#define LCD2S_CMD_CUR_BLINK_ON 0x18 +#define LCD2S_CMD_CUR_UL_ON 0x19 +#define LCD2S_CMD_DISPLAY_ON 0x1a +#define LCD2S_CMD_BACKLIGHT_OFF 0x20 +#define LCD2S_CMD_BACKLIGHT_ON 0x28 +#define LCD2S_CMD_WRITE 0x80 +#define LCD2S_CMD_SHIFT_UP 0x87 +#define LCD2S_CMD_SHIFT_DOWN 0x88 +#define LCD2S_CMD_CUR_ADDR 0x89 +#define LCD2S_CMD_CUR_POS 0x8a +#define LCD2S_CMD_CUR_RESET 0x8b +#define LCD2S_CMD_CLEAR 0x8c + +#define LCD2S_FIRST 8 +#define LCD2S_LAST 9 + +#define LCD2S_ROWS 4 +#define LCD2S_COLS 20 + +struct lcd2s_data { + struct i2c_client *i2c; + int row; + int col; + unsigned int cur_blink:1; + unsigned int cur_ul:1; +}; + +static struct lcd2s_data lcd2s; + +static void lcd2s_set_cursor(int row, int col) +{ + u8 buf[] = { LCD2S_CMD_CUR_POS, row + 1, col + 1}; + + if (row == lcd2s.row && col == lcd2s.col) + return; + + i2c_master_send(lcd2s.i2c, buf, sizeof(buf)); + lcd2s.row = row; + lcd2s.col = col; +} + +static void lcd2s_reset_cursor(void) +{ + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_CUR_RESET); + lcd2s.row = 0; + lcd2s.col = 0; +} + +static void lcd2s_increase_cursor(struct vc_data *con, int inc) +{ + lcd2s.col += inc; + if ((lcd2s.col / con->vc_cols) >= 1) { + lcd2s.row += (lcd2s.col / con->vc_cols); + lcd2s.col %= con->vc_cols; + } +} + +static void lcd2s_cursor_ul_on(void) +{ + if (!lcd2s.cur_ul) { + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_CUR_UL_ON); + lcd2s.cur_ul = 1; + } +} + +static void lcd2s_cursor_ul_off(void) +{ + if (lcd2s.cur_ul) { + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_CUR_UL_OFF); + lcd2s.cur_ul = 0; + } +} + +static void lcd2s_cursor_blink_on(void) +{ + if (!lcd2s.cur_blink) { + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_CUR_BLINK_ON); + lcd2s.cur_blink = 1; + } +} + +static void lcd2s_cursor_blink_off(void) +{ + if (lcd2s.cur_blink) { + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_CUR_BLINK_OFF); + lcd2s.cur_blink = 0; + } +} + +static const char *lcd2s_startup(void) +{ + return "lcd2s console"; +} + + +/* + * init is set if console is currently allocated during init + */ +static void lcd2s_init(struct vc_data *con, int init) +{ + lcd2s.cur_blink = 0; + lcd2s.cur_ul = 0; + + /* turn display on */ + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_DISPLAY_ON); + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_BACKLIGHT_ON); + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_CLEAR); + lcd2s_cursor_ul_on(); + lcd2s_reset_cursor(); + + con->vc_can_do_color = 0; + con->vc_hi_font_mask = 0; + + if (init) { + con->vc_rows = LCD2S_ROWS; + con->vc_cols = LCD2S_COLS; + } else + vc_resize(con, LCD2S_COLS, LCD2S_ROWS); +} + +static void lcd2s_deinit(struct vc_data *con) +{ + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_DISPLAY_OFF); + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_BACKLIGHT_OFF); + lcd2s_cursor_blink_off(); + lcd2s_cursor_ul_off(); +} + +static void lcd2s_clear(struct vc_data *con, int s_row, int s_col, + int height, int width) +{ + u8 buf[width]; + int i; + + if (width <= 0 || height <= 0) + return; + + /* if the whole display is to clear, we have a single command */ + if (s_col == 0 && s_row == 0 && + height >= con->vc_rows - 1 && width >= con->vc_cols - 1) { + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_CLEAR); + return; + } + + for (i = 0; i <= width; i++) + buf[i] = ' '; + + for (i = s_col; i <= height; i++) { + lcd2s_set_cursor(s_row, i); + i2c_master_send(lcd2s.i2c, buf, width); + } +} + +static void lcd2s_putc(struct vc_data *con, int data, int row, int col) +{ + u8 buf[] = {LCD2S_CMD_WRITE, data}; + + lcd2s_set_cursor(row, col); + + i2c_master_send(lcd2s.i2c, buf, sizeof(buf)); + lcd2s_increase_cursor(con, 1); +} + +static void lcd2s_putcs(struct vc_data *con, const unsigned short *buf, + int len, int row, int col) +{ + u8 *i2c_buf; + int i; + + i2c_buf = kzalloc(len + 1, GFP_KERNEL); + if (!i2c_buf) + return; + + lcd2s_set_cursor(row, col); + + i2c_buf[0] = LCD2S_CMD_WRITE; + for (i = 0; i < len ; i++) + i2c_buf[i + 1] = buf[i]; + + i2c_master_send(lcd2s.i2c, i2c_buf, len + 1); + kfree(i2c_buf); + lcd2s_increase_cursor(con, len); +} + +static void lcd2s_cursor(struct vc_data *con, int mode) +{ + switch (mode) { + case CM_ERASE: + lcd2s_cursor_blink_off(); + lcd2s_cursor_ul_off(); + break; + case CM_MOVE: + case CM_DRAW: + lcd2s_set_cursor(con->vc_y, con->vc_x); + + switch (con->vc_cursor_type & CUR_HWMASK) { + case CUR_UNDERLINE: + lcd2s_cursor_ul_on(); + lcd2s_cursor_blink_off(); + break; + case CUR_NONE: + lcd2s_cursor_blink_off(); + lcd2s_cursor_ul_off(); + break; + default: + lcd2s_cursor_blink_on(); + lcd2s_cursor_ul_off(); + break; + } + break; + } +} + +static int lcd2s_scroll(struct vc_data *con, int top, int bot, + int dir, int lines) +{ + /* we can only scroll the whole display */ + if (top == 0 && bot == con->vc_rows) { + while (lines--) { + switch (dir) { + case SM_UP: + i2c_smbus_write_byte(lcd2s.i2c, + LCD2S_CMD_SHIFT_UP); + break; + case SM_DOWN: + i2c_smbus_write_byte(lcd2s.i2c, + LCD2S_CMD_SHIFT_DOWN); + break; + } + } + return 0; + } + return 1; +} + +static void lcd2s_bmove(struct vc_data *conp, int s_row, int s_col, + int d_row, int d_col, int height, int width) +{ +} + +static int lcd2s_switch(struct vc_data *con) +{ + return 1; +} + +static int lcd2s_blank(struct vc_data *con, int blank, int mode_switch) +{ + switch (blank) { + case 0: /* unblank */ + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_DISPLAY_ON); + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_BACKLIGHT_ON); + break; + case 1: /* normal blanking */ + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_DISPLAY_OFF); + i2c_smbus_write_byte(lcd2s.i2c, LCD2S_CMD_BACKLIGHT_OFF); + break; + } + return 0; +} + +static int lcd2s_set_palette(struct vc_data *con, unsigned char *table) +{ + return -EINVAL; +} + +static int lcd2s_scrolldelta(struct vc_data *con, int lines) +{ + return 0; +} + +static struct consw lcd2s_con = { + .owner = THIS_MODULE, + .con_startup = lcd2s_startup, + .con_init = lcd2s_init, + .con_deinit = lcd2s_deinit, + .con_clear = lcd2s_clear, + .con_putc = lcd2s_putc, + .con_putcs = lcd2s_putcs, + .con_cursor = lcd2s_cursor, + .con_scroll = lcd2s_scroll, + .con_bmove = lcd2s_bmove, + .con_switch = lcd2s_switch, + .con_blank = lcd2s_blank, + .con_set_palette = lcd2s_set_palette, + .con_scrolldelta = lcd2s_scrolldelta, +}; + +static int __devinit lcd2s_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + if (!i2c_check_functionality(i2c->adapter, + I2C_FUNC_SMBUS_WRITE_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BLOCK_DATA)) + return -EIO; + + lcd2s.i2c = i2c; + + take_over_console(&lcd2s_con, LCD2S_FIRST, LCD2S_LAST, 1); + + return 0; +} + +static __devexit int lcd2s_i2c_remove(struct i2c_client *i2c) +{ + /* unregister from console subsystem */ + unregister_con_driver(&lcd2s_con); + return 0; +} + +static const struct i2c_device_id lcd2s_i2c_id[] = { + { "lcd2s", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lcd2s_i2c_id); + +static struct i2c_driver lcd2s_i2c_driver = { + .driver = { + .name = "lcd2s", + .owner = THIS_MODULE, + }, + .probe = lcd2s_i2c_probe, + .remove = __devexit_p(lcd2s_i2c_remove), + .id_table = lcd2s_i2c_id, +}; + +module_i2c_driver(lcd2s_i2c_driver) + +MODULE_DESCRIPTION("LCD2S character display console driver"); +MODULE_AUTHOR("Lars Poeschel"); +MODULE_LICENSE("GPL");