1 // SPDX-License-Identifier: GPL-2.0
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
7 * This is a driver allowing you to use a LCD2S 4x20 from Modtronix
8 * engineering as auxdisplay character device.
10 * (C) 2019 by Lemonage Software GmbH
11 * Author: Lars Pöschel <poeschel@lemonage.de>
12 * All rights reserved.
14 #include <linux/kernel.h>
15 #include <linux/mod_devicetable.h>
16 #include <linux/module.h>
17 #include <linux/property.h>
18 #include <linux/slab.h>
19 #include <linux/i2c.h>
20 #include <linux/delay.h>
24 #define LCD2S_CMD_CUR_MOVES_FWD 0x09
25 #define LCD2S_CMD_CUR_BLINK_OFF 0x10
26 #define LCD2S_CMD_CUR_UL_OFF 0x11
27 #define LCD2S_CMD_DISPLAY_OFF 0x12
28 #define LCD2S_CMD_CUR_BLINK_ON 0x18
29 #define LCD2S_CMD_CUR_UL_ON 0x19
30 #define LCD2S_CMD_DISPLAY_ON 0x1a
31 #define LCD2S_CMD_BACKLIGHT_OFF 0x20
32 #define LCD2S_CMD_BACKLIGHT_ON 0x28
33 #define LCD2S_CMD_WRITE 0x80
34 #define LCD2S_CMD_MOV_CUR_RIGHT 0x83
35 #define LCD2S_CMD_MOV_CUR_LEFT 0x84
36 #define LCD2S_CMD_SHIFT_RIGHT 0x85
37 #define LCD2S_CMD_SHIFT_LEFT 0x86
38 #define LCD2S_CMD_SHIFT_UP 0x87
39 #define LCD2S_CMD_SHIFT_DOWN 0x88
40 #define LCD2S_CMD_CUR_ADDR 0x89
41 #define LCD2S_CMD_CUR_POS 0x8a
42 #define LCD2S_CMD_CUR_RESET 0x8b
43 #define LCD2S_CMD_CLEAR 0x8c
44 #define LCD2S_CMD_DEF_CUSTOM_CHAR 0x92
45 #define LCD2S_CMD_READ_STATUS 0xd0
47 #define LCD2S_CHARACTER_SIZE 8
49 #define LCD2S_STATUS_BUF_MASK 0x7f
52 struct i2c_client
*i2c
;
53 struct charlcd
*charlcd
;
56 static s32
lcd2s_wait_buf_free(const struct i2c_client
*client
, int count
)
60 status
= i2c_smbus_read_byte_data(client
, LCD2S_CMD_READ_STATUS
);
64 while ((status
& LCD2S_STATUS_BUF_MASK
) < count
) {
66 status
= i2c_smbus_read_byte_data(client
,
67 LCD2S_CMD_READ_STATUS
);
74 static int lcd2s_i2c_master_send(const struct i2c_client
*client
,
75 const char *buf
, int count
)
79 status
= lcd2s_wait_buf_free(client
, count
);
83 return i2c_master_send(client
, buf
, count
);
86 static int lcd2s_i2c_smbus_write_byte(const struct i2c_client
*client
, u8 value
)
90 status
= lcd2s_wait_buf_free(client
, 1);
94 return i2c_smbus_write_byte(client
, value
);
97 static int lcd2s_print(struct charlcd
*lcd
, int c
)
99 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
100 u8 buf
[2] = { LCD2S_CMD_WRITE
, c
};
102 lcd2s_i2c_master_send(lcd2s
->i2c
, buf
, sizeof(buf
));
106 static int lcd2s_gotoxy(struct charlcd
*lcd
, unsigned int x
, unsigned int y
)
108 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
109 u8 buf
[3] = { LCD2S_CMD_CUR_POS
, y
+ 1, x
+ 1 };
111 lcd2s_i2c_master_send(lcd2s
->i2c
, buf
, sizeof(buf
));
116 static int lcd2s_home(struct charlcd
*lcd
)
118 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
120 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CUR_RESET
);
124 static int lcd2s_init_display(struct charlcd
*lcd
)
126 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
128 /* turn everything off, but display on */
129 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_DISPLAY_ON
);
130 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_BACKLIGHT_OFF
);
131 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CUR_MOVES_FWD
);
132 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CUR_BLINK_OFF
);
133 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CUR_UL_OFF
);
134 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CLEAR
);
139 static int lcd2s_shift_cursor(struct charlcd
*lcd
, enum charlcd_shift_dir dir
)
141 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
143 if (dir
== CHARLCD_SHIFT_LEFT
)
144 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_MOV_CUR_LEFT
);
146 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_MOV_CUR_RIGHT
);
151 static int lcd2s_shift_display(struct charlcd
*lcd
, enum charlcd_shift_dir dir
)
153 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
155 if (dir
== CHARLCD_SHIFT_LEFT
)
156 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_SHIFT_LEFT
);
158 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_SHIFT_RIGHT
);
163 static void lcd2s_backlight(struct charlcd
*lcd
, enum charlcd_onoff on
)
165 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
168 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_BACKLIGHT_ON
);
170 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_BACKLIGHT_OFF
);
173 static int lcd2s_display(struct charlcd
*lcd
, enum charlcd_onoff on
)
175 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
178 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_DISPLAY_ON
);
180 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_DISPLAY_OFF
);
185 static int lcd2s_cursor(struct charlcd
*lcd
, enum charlcd_onoff on
)
187 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
190 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CUR_UL_ON
);
192 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CUR_UL_OFF
);
197 static int lcd2s_blink(struct charlcd
*lcd
, enum charlcd_onoff on
)
199 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
202 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CUR_BLINK_ON
);
204 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CUR_BLINK_OFF
);
209 static int lcd2s_fontsize(struct charlcd
*lcd
, enum charlcd_fontsize size
)
214 static int lcd2s_lines(struct charlcd
*lcd
, enum charlcd_lines lines
)
220 * Generator: LGcxxxxx...xx; must have <c> between '0' and '7',
221 * representing the numerical ASCII code of the redefined character,
222 * and <xx...xx> a sequence of 16 hex digits representing 8 bytes
223 * for each character. Most LCDs will only use 5 lower bits of
226 static int lcd2s_redefine_char(struct charlcd
*lcd
, char *esc
)
228 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
229 u8 buf
[LCD2S_CHARACTER_SIZE
+ 2] = { LCD2S_CMD_DEF_CUSTOM_CHAR
};
233 if (!strchr(esc
, ';'))
238 buf
[1] = *(esc
++) - '0';
245 while (*esc
&& i
< LCD2S_CHARACTER_SIZE
+ 2) {
249 half
= hex_to_bin(*esc
++);
253 value
|= half
<< shift
;
260 lcd2s_i2c_master_send(lcd2s
->i2c
, buf
, sizeof(buf
));
264 static int lcd2s_clear_display(struct charlcd
*lcd
)
266 struct lcd2s_data
*lcd2s
= lcd
->drvdata
;
268 /* This implicitly sets cursor to first row and column */
269 lcd2s_i2c_smbus_write_byte(lcd2s
->i2c
, LCD2S_CMD_CLEAR
);
273 static const struct charlcd_ops lcd2s_ops
= {
274 .print
= lcd2s_print
,
275 .backlight
= lcd2s_backlight
,
276 .gotoxy
= lcd2s_gotoxy
,
278 .clear_display
= lcd2s_clear_display
,
279 .init_display
= lcd2s_init_display
,
280 .shift_cursor
= lcd2s_shift_cursor
,
281 .shift_display
= lcd2s_shift_display
,
282 .display
= lcd2s_display
,
283 .cursor
= lcd2s_cursor
,
284 .blink
= lcd2s_blink
,
285 .fontsize
= lcd2s_fontsize
,
286 .lines
= lcd2s_lines
,
287 .redefine_char
= lcd2s_redefine_char
,
290 static int lcd2s_i2c_probe(struct i2c_client
*i2c
)
293 struct lcd2s_data
*lcd2s
;
296 if (!i2c_check_functionality(i2c
->adapter
,
297 I2C_FUNC_SMBUS_WRITE_BYTE_DATA
|
298 I2C_FUNC_SMBUS_WRITE_BLOCK_DATA
))
301 lcd2s
= devm_kzalloc(&i2c
->dev
, sizeof(*lcd2s
), GFP_KERNEL
);
305 /* Test, if the display is responding */
306 err
= lcd2s_i2c_smbus_write_byte(i2c
, LCD2S_CMD_DISPLAY_OFF
);
310 lcd
= charlcd_alloc();
314 lcd
->drvdata
= lcd2s
;
316 lcd2s
->charlcd
= lcd
;
318 /* Required properties */
319 err
= device_property_read_u32(&i2c
->dev
, "display-height-chars",
324 err
= device_property_read_u32(&i2c
->dev
, "display-width-chars",
329 lcd
->ops
= &lcd2s_ops
;
331 err
= charlcd_register(lcd2s
->charlcd
);
335 i2c_set_clientdata(i2c
, lcd2s
);
339 charlcd_free(lcd2s
->charlcd
);
343 static void lcd2s_i2c_remove(struct i2c_client
*i2c
)
345 struct lcd2s_data
*lcd2s
= i2c_get_clientdata(i2c
);
347 charlcd_unregister(lcd2s
->charlcd
);
348 charlcd_free(lcd2s
->charlcd
);
351 static const struct i2c_device_id lcd2s_i2c_id
[] = {
355 MODULE_DEVICE_TABLE(i2c
, lcd2s_i2c_id
);
357 static const struct of_device_id lcd2s_of_table
[] = {
358 { .compatible
= "modtronix,lcd2s" },
361 MODULE_DEVICE_TABLE(of
, lcd2s_of_table
);
363 static struct i2c_driver lcd2s_i2c_driver
= {
366 .of_match_table
= lcd2s_of_table
,
368 .probe
= lcd2s_i2c_probe
,
369 .remove
= lcd2s_i2c_remove
,
370 .id_table
= lcd2s_i2c_id
,
372 module_i2c_driver(lcd2s_i2c_driver
);
374 MODULE_DESCRIPTION("LCD2S character display driver");
375 MODULE_AUTHOR("Lars Poeschel");
376 MODULE_LICENSE("GPL");