linux/drivers/auxdisplay/lcd2s.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 *  console driver for LCD2S 4x20 character displays connected through i2c.
   4 *  The display also has a spi interface, but the driver does not support
   5 *  this yet.
   6 *
   7 *  This is a driver allowing you to use a LCD2S 4x20 from modtronix
   8 *  engineering as auxdisplay character device.
   9 *
  10 *  (C) 2019 by Lemonage Software GmbH
  11 *  Author: Lars Pöschel <poeschel@lemonage.de>
  12 *  All rights reserved.
  13 */
  14#include <linux/kernel.h>
  15#include <linux/module.h>
  16#include <linux/slab.h>
  17#include <linux/i2c.h>
  18#include <linux/delay.h>
  19
  20#include "charlcd.h"
  21
  22#define LCD2S_CMD_CUR_MOVES_FWD         0x09
  23#define LCD2S_CMD_CUR_BLINK_OFF         0x10
  24#define LCD2S_CMD_CUR_UL_OFF            0x11
  25#define LCD2S_CMD_DISPLAY_OFF           0x12
  26#define LCD2S_CMD_CUR_BLINK_ON          0x18
  27#define LCD2S_CMD_CUR_UL_ON             0x19
  28#define LCD2S_CMD_DISPLAY_ON            0x1a
  29#define LCD2S_CMD_BACKLIGHT_OFF         0x20
  30#define LCD2S_CMD_BACKLIGHT_ON          0x28
  31#define LCD2S_CMD_WRITE                 0x80
  32#define LCD2S_CMD_MOV_CUR_RIGHT         0x83
  33#define LCD2S_CMD_MOV_CUR_LEFT          0x84
  34#define LCD2S_CMD_SHIFT_RIGHT           0x85
  35#define LCD2S_CMD_SHIFT_LEFT            0x86
  36#define LCD2S_CMD_SHIFT_UP              0x87
  37#define LCD2S_CMD_SHIFT_DOWN            0x88
  38#define LCD2S_CMD_CUR_ADDR              0x89
  39#define LCD2S_CMD_CUR_POS               0x8a
  40#define LCD2S_CMD_CUR_RESET             0x8b
  41#define LCD2S_CMD_CLEAR                 0x8c
  42#define LCD2S_CMD_DEF_CUSTOM_CHAR       0x92
  43#define LCD2S_CMD_READ_STATUS           0xd0
  44
  45#define LCD2S_CHARACTER_SIZE            8
  46
  47#define LCD2S_STATUS_BUF_MASK           0x7f
  48
  49struct lcd2s_data {
  50        struct i2c_client *i2c;
  51        struct charlcd *charlcd;
  52};
  53
  54static s32 lcd2s_wait_buf_free(const struct i2c_client *client, int count)
  55{
  56        s32 status;
  57
  58        status = i2c_smbus_read_byte_data(client, LCD2S_CMD_READ_STATUS);
  59        if (status < 0)
  60                return status;
  61
  62        while ((status & LCD2S_STATUS_BUF_MASK) < count) {
  63                mdelay(1);
  64                status = i2c_smbus_read_byte_data(client,
  65                                                  LCD2S_CMD_READ_STATUS);
  66                if (status < 0)
  67                        return status;
  68        }
  69        return 0;
  70}
  71
  72static int lcd2s_i2c_master_send(const struct i2c_client *client,
  73                                 const char *buf, int count)
  74{
  75        s32 status;
  76
  77        status = lcd2s_wait_buf_free(client, count);
  78        if (status < 0)
  79                return status;
  80
  81        return i2c_master_send(client, buf, count);
  82}
  83
  84static int lcd2s_i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
  85{
  86        s32 status;
  87
  88        status = lcd2s_wait_buf_free(client, 1);
  89        if (status < 0)
  90                return status;
  91
  92        return i2c_smbus_write_byte(client, value);
  93}
  94
  95static int lcd2s_print(struct charlcd *lcd, int c)
  96{
  97        struct lcd2s_data *lcd2s = lcd->drvdata;
  98        u8 buf[2] = { LCD2S_CMD_WRITE, c };
  99
 100        lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
 101        return 0;
 102}
 103
 104static int lcd2s_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y)
 105{
 106        struct lcd2s_data *lcd2s = lcd->drvdata;
 107        u8 buf[] = { LCD2S_CMD_CUR_POS, y + 1, x + 1};
 108
 109        lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
 110
 111        return 0;
 112}
 113
 114static int lcd2s_home(struct charlcd *lcd)
 115{
 116        struct lcd2s_data *lcd2s = lcd->drvdata;
 117
 118        lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_RESET);
 119        return 0;
 120}
 121
 122static int lcd2s_init_display(struct charlcd *lcd)
 123{
 124        struct lcd2s_data *lcd2s = lcd->drvdata;
 125
 126        /* turn everything off, but display on */
 127        lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
 128        lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
 129        lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_MOVES_FWD);
 130        lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
 131        lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
 132        lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
 133
 134        return 0;
 135}
 136
 137static int lcd2s_shift_cursor(struct charlcd *lcd, enum charlcd_shift_dir dir)
 138{
 139        struct lcd2s_data *lcd2s = lcd->drvdata;
 140
 141        if (dir == CHARLCD_SHIFT_LEFT)
 142                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_LEFT);
 143        else
 144                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_RIGHT);
 145
 146        return 0;
 147}
 148
 149static int lcd2s_shift_display(struct charlcd *lcd, enum charlcd_shift_dir dir)
 150{
 151        struct lcd2s_data *lcd2s = lcd->drvdata;
 152
 153        if (dir == CHARLCD_SHIFT_LEFT)
 154                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_LEFT);
 155        else
 156                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_RIGHT);
 157
 158        return 0;
 159}
 160
 161static void lcd2s_backlight(struct charlcd *lcd, enum charlcd_onoff on)
 162{
 163        struct lcd2s_data *lcd2s = lcd->drvdata;
 164
 165        if (on)
 166                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_ON);
 167        else
 168                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
 169}
 170
 171static int lcd2s_display(struct charlcd *lcd, enum charlcd_onoff on)
 172{
 173        struct lcd2s_data *lcd2s = lcd->drvdata;
 174
 175        if (on)
 176                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
 177        else
 178                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_OFF);
 179
 180        return 0;
 181}
 182
 183static int lcd2s_cursor(struct charlcd *lcd, enum charlcd_onoff on)
 184{
 185        struct lcd2s_data *lcd2s = lcd->drvdata;
 186
 187        if (on)
 188                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_ON);
 189        else
 190                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
 191
 192        return 0;
 193}
 194
 195static int lcd2s_blink(struct charlcd *lcd, enum charlcd_onoff on)
 196{
 197        struct lcd2s_data *lcd2s = lcd->drvdata;
 198
 199        if (on)
 200                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_ON);
 201        else
 202                lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
 203
 204        return 0;
 205}
 206
 207static int lcd2s_fontsize(struct charlcd *lcd, enum charlcd_fontsize size)
 208{
 209        return 0;
 210}
 211
 212static int lcd2s_lines(struct charlcd *lcd, enum charlcd_lines lines)
 213{
 214        return 0;
 215}
 216
 217static int lcd2s_redefine_char(struct charlcd *lcd, char *esc)
 218{
 219        /* Generator : LGcxxxxx...xx; must have <c> between '0'
 220         * and '7', representing the numerical ASCII code of the
 221         * redefined character, and <xx...xx> a sequence of 16
 222         * hex digits representing 8 bytes for each character.
 223         * Most LCDs will only use 5 lower bits of the 7 first
 224         * bytes.
 225         */
 226
 227        struct lcd2s_data *lcd2s = lcd->drvdata;
 228        u8 buf[LCD2S_CHARACTER_SIZE + 2] = { LCD2S_CMD_DEF_CUSTOM_CHAR };
 229        u8 value;
 230        int shift, i;
 231
 232        if (!strchr(esc, ';'))
 233                return 0;
 234
 235        esc++;
 236
 237        buf[1] = *(esc++) - '0';
 238        if (buf[1] > 7)
 239                return 1;
 240
 241        i = 0;
 242        shift = 0;
 243        value = 0;
 244        while (*esc && i < LCD2S_CHARACTER_SIZE + 2) {
 245                int half;
 246
 247                shift ^= 4;
 248                half = hex_to_bin(*esc++);
 249                if (half < 0)
 250                        continue;
 251
 252                value |= half << shift;
 253                if (shift == 0) {
 254                        buf[i++] = value;
 255                        value = 0;
 256                }
 257        }
 258
 259        lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
 260        return 1;
 261}
 262
 263static int lcd2s_clear_display(struct charlcd *lcd)
 264{
 265        struct lcd2s_data *lcd2s = lcd->drvdata;
 266
 267        /* This implicitly sets cursor to first row and column */
 268        lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
 269        return 0;
 270}
 271
 272static const struct charlcd_ops lcd2s_ops = {
 273        .print          = lcd2s_print,
 274        .backlight      = lcd2s_backlight,
 275        .gotoxy         = lcd2s_gotoxy,
 276        .home           = lcd2s_home,
 277        .clear_display  = lcd2s_clear_display,
 278        .init_display   = lcd2s_init_display,
 279        .shift_cursor   = lcd2s_shift_cursor,
 280        .shift_display  = lcd2s_shift_display,
 281        .display        = lcd2s_display,
 282        .cursor         = lcd2s_cursor,
 283        .blink          = lcd2s_blink,
 284        .fontsize       = lcd2s_fontsize,
 285        .lines          = lcd2s_lines,
 286        .redefine_char  = lcd2s_redefine_char,
 287};
 288
 289static int lcd2s_i2c_probe(struct i2c_client *i2c,
 290                                const struct i2c_device_id *id)
 291{
 292        struct charlcd *lcd;
 293        struct lcd2s_data *lcd2s;
 294        int err;
 295
 296        if (!i2c_check_functionality(i2c->adapter,
 297                        I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
 298                        I2C_FUNC_SMBUS_WRITE_BLOCK_DATA))
 299                return -EIO;
 300
 301        /* Test, if the display is responding */
 302        err = lcd2s_i2c_smbus_write_byte(i2c, LCD2S_CMD_DISPLAY_OFF);
 303        if (err < 0)
 304                return err;
 305
 306        lcd = charlcd_alloc();
 307        if (!lcd)
 308                return -ENOMEM;
 309
 310        lcd2s = kzalloc(sizeof(struct lcd2s_data), GFP_KERNEL);
 311        if (!lcd2s) {
 312                err = -ENOMEM;
 313                goto fail1;
 314        }
 315
 316        lcd->drvdata = lcd2s;
 317        lcd2s->i2c = i2c;
 318        lcd2s->charlcd = lcd;
 319
 320        /* Required properties */
 321        err = device_property_read_u32(&i2c->dev, "display-height-chars",
 322                        &lcd->height);
 323        if (err)
 324                goto fail2;
 325
 326        err = device_property_read_u32(&i2c->dev, "display-width-chars",
 327                        &lcd->width);
 328        if (err)
 329                goto fail2;
 330
 331        lcd->ops = &lcd2s_ops;
 332
 333        err = charlcd_register(lcd2s->charlcd);
 334        if (err)
 335                goto fail2;
 336
 337        i2c_set_clientdata(i2c, lcd2s);
 338        return 0;
 339
 340fail2:
 341        kfree(lcd2s);
 342fail1:
 343        kfree(lcd);
 344        return err;
 345}
 346
 347static int lcd2s_i2c_remove(struct i2c_client *i2c)
 348{
 349        struct lcd2s_data *lcd2s = i2c_get_clientdata(i2c);
 350
 351        charlcd_unregister(lcd2s->charlcd);
 352        kfree(lcd2s->charlcd);
 353        return 0;
 354}
 355
 356static const struct i2c_device_id lcd2s_i2c_id[] = {
 357        { "lcd2s", 0 },
 358        { }
 359};
 360MODULE_DEVICE_TABLE(i2c, lcd2s_i2c_id);
 361
 362#ifdef CONFIG_OF
 363static const struct of_device_id lcd2s_of_table[] = {
 364        { .compatible = "modtronix,lcd2s" },
 365        { }
 366};
 367MODULE_DEVICE_TABLE(of, lcd2s_of_table);
 368#endif
 369
 370static struct i2c_driver lcd2s_i2c_driver = {
 371        .driver = {
 372                .name = "lcd2s",
 373#ifdef CONFIG_OF
 374                .of_match_table = of_match_ptr(lcd2s_of_table),
 375#endif
 376        },
 377        .probe = lcd2s_i2c_probe,
 378        .remove = lcd2s_i2c_remove,
 379        .id_table = lcd2s_i2c_id,
 380};
 381
 382static int __init lcd2s_modinit(void)
 383{
 384        int ret = 0;
 385
 386        ret = i2c_add_driver(&lcd2s_i2c_driver);
 387        if (ret != 0)
 388                pr_err("Failed to register lcd2s driver\n");
 389
 390        return ret;
 391}
 392module_init(lcd2s_modinit)
 393
 394static void __exit lcd2s_exit(void)
 395{
 396        i2c_del_driver(&lcd2s_i2c_driver);
 397}
 398module_exit(lcd2s_exit)
 399
 400MODULE_DESCRIPTION("LCD2S character display driver");
 401MODULE_AUTHOR("Lars Poeschel");
 402MODULE_LICENSE("GPL");
 403