From patchwork Tue Nov 20 12:56:46 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: larsi@wh2.tu-dresden.de X-Patchwork-Id: 1773341 Return-Path: X-Original-To: patchwork-linux-fbdev@patchwork.kernel.org Delivered-To: patchwork-process-083081@patchwork1.kernel.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by patchwork1.kernel.org (Postfix) with ESMTP id B06633FCAE for ; Tue, 20 Nov 2012 13:06:46 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753128Ab2KTNGq (ORCPT ); Tue, 20 Nov 2012 08:06:46 -0500 Received: from atlantis.wh2.tu-dresden.de ([141.30.228.39]:46777 "EHLO atlantis.wh2.tu-dresden.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753127Ab2KTNGq (ORCPT ); Tue, 20 Nov 2012 08:06:46 -0500 X-Greylist: delayed 626 seconds by postgrey-1.27 at vger.kernel.org; Tue, 20 Nov 2012 08:06:45 EST Received: from lem-wkst-02.routerb3c0c6.com (p50998852.dip0.t-ipconnect.de [80.153.136.82]) by atlantis.wh2.tu-dresden.de (Postfix) with ESMTPA id 6114583A6CA; Tue, 20 Nov 2012 13:56:17 +0100 (CET) From: Lars Poeschel To: FlorianSchandinat@gmx.de Cc: poeschel@lemonage.de, mathieu.poirier@linaro.org, arnd@arndb.de, linux-fbdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH] video console: add a driver for lcd2s character display Date: Tue, 20 Nov 2012 13:56:46 +0100 Message-Id: <1353416206-3243-1-git-send-email-larsi@wh2.tu-dresden.de> X-Mailer: git-send-email 1.7.10.4 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 | 396 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 407 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..0d967ba --- /dev/null +++ b/drivers/video/console/lcd2scon.c @@ -0,0 +1,396 @@ +/* + * 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; + struct consw consw; + int row; + int col; + unsigned int cur_blink:1; + unsigned int cur_ul:1; +}; + +#define consw_to_lcd2s_data(x) container_of(x, struct lcd2s_data, consw) + +static void lcd2s_set_cursor(struct lcd2s_data *lcd2s, 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(struct lcd2s_data *lcd2s) +{ + i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_RESET); + lcd2s->row = 0; + lcd2s->col = 0; +} + +static void lcd2s_increase_cursor(struct lcd2s_data *lcd2s, + 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(struct lcd2s_data *lcd2s) +{ + 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(struct lcd2s_data *lcd2s) +{ + 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(struct lcd2s_data *lcd2s) +{ + 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(struct lcd2s_data *lcd2s) +{ + 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) +{ + struct lcd2s_data *lcd2s = consw_to_lcd2s_data(con->vc_sw); + + 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); + lcd2s_reset_cursor(lcd2s); + + 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) +{ + struct lcd2s_data *lcd2s = consw_to_lcd2s_data(con->vc_sw); + + 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); + lcd2s_cursor_ul_off(lcd2s); +} + +static void lcd2s_clear(struct vc_data *con, int s_row, int s_col, + int height, int width) +{ + u8 buf[width]; + int i; + struct lcd2s_data *lcd2s = consw_to_lcd2s_data(con->vc_sw); + + 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(lcd2s, s_row, i); + i2c_master_send(lcd2s->i2c, buf, width); + } +} + +static void lcd2s_putc(struct vc_data *con, int data, int row, int col) +{ + struct lcd2s_data *lcd2s = consw_to_lcd2s_data(con->vc_sw); + u8 buf[] = {LCD2S_CMD_WRITE, data}; + + lcd2s_set_cursor(lcd2s, row, col); + + i2c_master_send(lcd2s->i2c, buf, sizeof(buf)); + lcd2s_increase_cursor(lcd2s, 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; + struct lcd2s_data *lcd2s = consw_to_lcd2s_data(con->vc_sw); + + i2c_buf = kzalloc(len + 1, GFP_KERNEL); + if (!i2c_buf) + return; + + lcd2s_set_cursor(lcd2s, 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(lcd2s, con, len); +} + +static void lcd2s_cursor(struct vc_data *con, int mode) +{ + struct lcd2s_data *lcd2s = consw_to_lcd2s_data(con->vc_sw); + + switch (mode) { + case CM_ERASE: + lcd2s_cursor_blink_off(lcd2s); + lcd2s_cursor_ul_off(lcd2s); + break; + case CM_MOVE: + case CM_DRAW: + lcd2s_set_cursor(lcd2s, con->vc_y, con->vc_x); + + switch (con->vc_cursor_type & CUR_HWMASK) { + case CUR_UNDERLINE: + lcd2s_cursor_ul_on(lcd2s); + lcd2s_cursor_blink_off(lcd2s); + break; + case CUR_NONE: + lcd2s_cursor_blink_off(lcd2s); + lcd2s_cursor_ul_off(lcd2s); + break; + default: + lcd2s_cursor_blink_on(lcd2s); + lcd2s_cursor_ul_off(lcd2s); + break; + } + break; + } +} + +static int lcd2s_scroll(struct vc_data *con, int top, int bot, + int dir, int lines) +{ + struct lcd2s_data *lcd2s = consw_to_lcd2s_data(con->vc_sw); + + /* 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) +{ + struct lcd2s_data *lcd2s = consw_to_lcd2s_data(con->vc_sw); + + 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 int __devinit lcd2s_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct lcd2s_data *data; + + if (!i2c_check_functionality(i2c->adapter, + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) + return -EIO; + data = devm_kzalloc(&i2c->dev, sizeof(struct lcd2s_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->i2c = i2c; + data->consw.owner = THIS_MODULE; + data->consw.con_startup = lcd2s_startup; + data->consw.con_init = lcd2s_init; + data->consw.con_deinit = lcd2s_deinit; + data->consw.con_clear = lcd2s_clear; + data->consw.con_putc = lcd2s_putc; + data->consw.con_putcs = lcd2s_putcs; + data->consw.con_cursor = lcd2s_cursor; + data->consw.con_scroll = lcd2s_scroll; + data->consw.con_bmove = lcd2s_bmove; + data->consw.con_switch = lcd2s_switch; + data->consw.con_blank = lcd2s_blank; + data->consw.con_set_palette = lcd2s_set_palette; + data->consw.con_scrolldelta = lcd2s_scrolldelta; + + i2c_set_clientdata(i2c, data); + + take_over_console(&data->consw, LCD2S_FIRST, LCD2S_LAST, 1); + + return 0; +} + +static __devexit int lcd2s_i2c_remove(struct i2c_client *i2c) +{ + struct lcd2s_data *data = i2c_get_clientdata(i2c); + + /* unregister from console subsystem */ + unregister_con_driver(&data->consw); + 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, +}; + +static int __init lcd2s_modinit(void) +{ + int ret = 0; + + ret = i2c_add_driver(&lcd2s_i2c_driver); + if (ret != 0) + pr_err("Failed to register lcd2s driver\n"); + + return ret; +} +module_init(lcd2s_modinit) + +static void __exit lcd2s_exit(void) +{ + i2c_del_driver(&lcd2s_i2c_driver); +} +module_exit(lcd2s_exit) + +MODULE_DESCRIPTION("LCD2S character display console driver"); +MODULE_AUTHOR("Lars Poeschel"); +MODULE_LICENSE("GPL");