Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/auxdisplay/lcd2s.c
26278 views
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 <[email protected]>
12
* All rights reserved.
13
*/
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>
21
22
#include "charlcd.h"
23
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
46
47
#define LCD2S_CHARACTER_SIZE 8
48
49
#define LCD2S_STATUS_BUF_MASK 0x7f
50
51
struct lcd2s_data {
52
struct i2c_client *i2c;
53
struct charlcd *charlcd;
54
};
55
56
static s32 lcd2s_wait_buf_free(const struct i2c_client *client, int count)
57
{
58
s32 status;
59
60
status = i2c_smbus_read_byte_data(client, LCD2S_CMD_READ_STATUS);
61
if (status < 0)
62
return status;
63
64
while ((status & LCD2S_STATUS_BUF_MASK) < count) {
65
mdelay(1);
66
status = i2c_smbus_read_byte_data(client,
67
LCD2S_CMD_READ_STATUS);
68
if (status < 0)
69
return status;
70
}
71
return 0;
72
}
73
74
static int lcd2s_i2c_master_send(const struct i2c_client *client,
75
const char *buf, int count)
76
{
77
s32 status;
78
79
status = lcd2s_wait_buf_free(client, count);
80
if (status < 0)
81
return status;
82
83
return i2c_master_send(client, buf, count);
84
}
85
86
static int lcd2s_i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
87
{
88
s32 status;
89
90
status = lcd2s_wait_buf_free(client, 1);
91
if (status < 0)
92
return status;
93
94
return i2c_smbus_write_byte(client, value);
95
}
96
97
static int lcd2s_print(struct charlcd *lcd, int c)
98
{
99
struct lcd2s_data *lcd2s = lcd->drvdata;
100
u8 buf[2] = { LCD2S_CMD_WRITE, c };
101
102
lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
103
return 0;
104
}
105
106
static int lcd2s_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y)
107
{
108
struct lcd2s_data *lcd2s = lcd->drvdata;
109
u8 buf[3] = { LCD2S_CMD_CUR_POS, y + 1, x + 1 };
110
111
lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
112
113
return 0;
114
}
115
116
static int lcd2s_home(struct charlcd *lcd)
117
{
118
struct lcd2s_data *lcd2s = lcd->drvdata;
119
120
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_RESET);
121
return 0;
122
}
123
124
static int lcd2s_init_display(struct charlcd *lcd)
125
{
126
struct lcd2s_data *lcd2s = lcd->drvdata;
127
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);
135
136
return 0;
137
}
138
139
static int lcd2s_shift_cursor(struct charlcd *lcd, enum charlcd_shift_dir dir)
140
{
141
struct lcd2s_data *lcd2s = lcd->drvdata;
142
143
if (dir == CHARLCD_SHIFT_LEFT)
144
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_LEFT);
145
else
146
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_RIGHT);
147
148
return 0;
149
}
150
151
static int lcd2s_shift_display(struct charlcd *lcd, enum charlcd_shift_dir dir)
152
{
153
struct lcd2s_data *lcd2s = lcd->drvdata;
154
155
if (dir == CHARLCD_SHIFT_LEFT)
156
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_LEFT);
157
else
158
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_RIGHT);
159
160
return 0;
161
}
162
163
static void lcd2s_backlight(struct charlcd *lcd, enum charlcd_onoff on)
164
{
165
struct lcd2s_data *lcd2s = lcd->drvdata;
166
167
if (on)
168
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_ON);
169
else
170
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
171
}
172
173
static int lcd2s_display(struct charlcd *lcd, enum charlcd_onoff on)
174
{
175
struct lcd2s_data *lcd2s = lcd->drvdata;
176
177
if (on)
178
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
179
else
180
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_OFF);
181
182
return 0;
183
}
184
185
static int lcd2s_cursor(struct charlcd *lcd, enum charlcd_onoff on)
186
{
187
struct lcd2s_data *lcd2s = lcd->drvdata;
188
189
if (on)
190
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_ON);
191
else
192
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
193
194
return 0;
195
}
196
197
static int lcd2s_blink(struct charlcd *lcd, enum charlcd_onoff on)
198
{
199
struct lcd2s_data *lcd2s = lcd->drvdata;
200
201
if (on)
202
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_ON);
203
else
204
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
205
206
return 0;
207
}
208
209
static int lcd2s_fontsize(struct charlcd *lcd, enum charlcd_fontsize size)
210
{
211
return 0;
212
}
213
214
static int lcd2s_lines(struct charlcd *lcd, enum charlcd_lines lines)
215
{
216
return 0;
217
}
218
219
/*
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
224
* the 7 first bytes.
225
*/
226
static int lcd2s_redefine_char(struct charlcd *lcd, char *esc)
227
{
228
struct lcd2s_data *lcd2s = lcd->drvdata;
229
u8 buf[LCD2S_CHARACTER_SIZE + 2] = { LCD2S_CMD_DEF_CUSTOM_CHAR };
230
u8 value;
231
int shift, i;
232
233
if (!strchr(esc, ';'))
234
return 0;
235
236
esc++;
237
238
buf[1] = *(esc++) - '0';
239
if (buf[1] > 7)
240
return 1;
241
242
i = 2;
243
shift = 0;
244
value = 0;
245
while (*esc && i < LCD2S_CHARACTER_SIZE + 2) {
246
int half;
247
248
shift ^= 4;
249
half = hex_to_bin(*esc++);
250
if (half < 0)
251
continue;
252
253
value |= half << shift;
254
if (shift == 0) {
255
buf[i++] = value;
256
value = 0;
257
}
258
}
259
260
lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
261
return 1;
262
}
263
264
static int lcd2s_clear_display(struct charlcd *lcd)
265
{
266
struct lcd2s_data *lcd2s = lcd->drvdata;
267
268
/* This implicitly sets cursor to first row and column */
269
lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
270
return 0;
271
}
272
273
static const struct charlcd_ops lcd2s_ops = {
274
.print = lcd2s_print,
275
.backlight = lcd2s_backlight,
276
.gotoxy = lcd2s_gotoxy,
277
.home = lcd2s_home,
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,
288
};
289
290
static int lcd2s_i2c_probe(struct i2c_client *i2c)
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(sizeof(*lcd2s));
307
if (!lcd)
308
return -ENOMEM;
309
310
lcd->ops = &lcd2s_ops;
311
312
lcd2s = lcd->drvdata;
313
lcd2s->i2c = i2c;
314
lcd2s->charlcd = lcd;
315
316
/* Required properties */
317
err = device_property_read_u32(&i2c->dev, "display-height-chars",
318
&lcd->height);
319
if (err)
320
goto fail1;
321
322
err = device_property_read_u32(&i2c->dev, "display-width-chars",
323
&lcd->width);
324
if (err)
325
goto fail1;
326
327
err = charlcd_register(lcd2s->charlcd);
328
if (err)
329
goto fail1;
330
331
i2c_set_clientdata(i2c, lcd2s);
332
return 0;
333
334
fail1:
335
charlcd_free(lcd2s->charlcd);
336
return err;
337
}
338
339
static void lcd2s_i2c_remove(struct i2c_client *i2c)
340
{
341
struct lcd2s_data *lcd2s = i2c_get_clientdata(i2c);
342
343
charlcd_unregister(lcd2s->charlcd);
344
charlcd_free(lcd2s->charlcd);
345
}
346
347
static const struct i2c_device_id lcd2s_i2c_id[] = {
348
{ "lcd2s" },
349
{ }
350
};
351
MODULE_DEVICE_TABLE(i2c, lcd2s_i2c_id);
352
353
static const struct of_device_id lcd2s_of_table[] = {
354
{ .compatible = "modtronix,lcd2s" },
355
{ }
356
};
357
MODULE_DEVICE_TABLE(of, lcd2s_of_table);
358
359
static struct i2c_driver lcd2s_i2c_driver = {
360
.driver = {
361
.name = "lcd2s",
362
.of_match_table = lcd2s_of_table,
363
},
364
.probe = lcd2s_i2c_probe,
365
.remove = lcd2s_i2c_remove,
366
.id_table = lcd2s_i2c_id,
367
};
368
module_i2c_driver(lcd2s_i2c_driver);
369
370
MODULE_DESCRIPTION("LCD2S character display driver");
371
MODULE_AUTHOR("Lars Poeschel");
372
MODULE_LICENSE("GPL");
373
374