summaryrefslogtreecommitdiff
path: root/drivers/auxdisplay/lcd2s.c
blob: e465108d9998a1563a646ddb3fef6309a98337b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
// SPDX-License-Identifier: GPL-2.0
/*
 *  Console driver for LCD2S 4x20 character displays connected through i2c.
 *  The display also has a SPI interface, but the driver does not support
 *  this yet.
 *
 *  This is a driver allowing you to use a LCD2S 4x20 from Modtronix
 *  engineering as auxdisplay character device.
 *
 *  (C) 2019 by Lemonage Software GmbH
 *  Author: Lars Pöschel <poeschel@lemonage.de>
 *  All rights reserved.
 */
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/property.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>

#include "charlcd.h"

#define LCD2S_CMD_CUR_MOVES_FWD		0x09
#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_MOV_CUR_RIGHT		0x83
#define LCD2S_CMD_MOV_CUR_LEFT		0x84
#define LCD2S_CMD_SHIFT_RIGHT		0x85
#define LCD2S_CMD_SHIFT_LEFT		0x86
#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_CMD_DEF_CUSTOM_CHAR	0x92
#define LCD2S_CMD_READ_STATUS		0xd0

#define LCD2S_CHARACTER_SIZE		8

#define LCD2S_STATUS_BUF_MASK		0x7f

struct lcd2s_data {
	struct i2c_client *i2c;
	struct charlcd *charlcd;
};

static s32 lcd2s_wait_buf_free(const struct i2c_client *client, int count)
{
	s32 status;

	status = i2c_smbus_read_byte_data(client, LCD2S_CMD_READ_STATUS);
	if (status < 0)
		return status;

	while ((status & LCD2S_STATUS_BUF_MASK) < count) {
		mdelay(1);
		status = i2c_smbus_read_byte_data(client,
						  LCD2S_CMD_READ_STATUS);
		if (status < 0)
			return status;
	}
	return 0;
}

static int lcd2s_i2c_master_send(const struct i2c_client *client,
				 const char *buf, int count)
{
	s32 status;

	status = lcd2s_wait_buf_free(client, count);
	if (status < 0)
		return status;

	return i2c_master_send(client, buf, count);
}

static int lcd2s_i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
{
	s32 status;

	status = lcd2s_wait_buf_free(client, 1);
	if (status < 0)
		return status;

	return i2c_smbus_write_byte(client, value);
}

static int lcd2s_print(struct charlcd *lcd, int c)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;
	u8 buf[2] = { LCD2S_CMD_WRITE, c };

	lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
	return 0;
}

static int lcd2s_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;
	u8 buf[3] = { LCD2S_CMD_CUR_POS, y + 1, x + 1 };

	lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));

	return 0;
}

static int lcd2s_home(struct charlcd *lcd)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_RESET);
	return 0;
}

static int lcd2s_init_display(struct charlcd *lcd)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	/* turn everything off, but display on */
	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_MOVES_FWD);
	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);

	return 0;
}

static int lcd2s_shift_cursor(struct charlcd *lcd, enum charlcd_shift_dir dir)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	if (dir == CHARLCD_SHIFT_LEFT)
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_LEFT);
	else
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_RIGHT);

	return 0;
}

static int lcd2s_shift_display(struct charlcd *lcd, enum charlcd_shift_dir dir)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	if (dir == CHARLCD_SHIFT_LEFT)
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_LEFT);
	else
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_RIGHT);

	return 0;
}

static void lcd2s_backlight(struct charlcd *lcd, enum charlcd_onoff on)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	if (on)
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_ON);
	else
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
}

static int lcd2s_display(struct charlcd *lcd, enum charlcd_onoff on)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	if (on)
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
	else
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_OFF);

	return 0;
}

static int lcd2s_cursor(struct charlcd *lcd, enum charlcd_onoff on)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	if (on)
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_ON);
	else
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);

	return 0;
}

static int lcd2s_blink(struct charlcd *lcd, enum charlcd_onoff on)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	if (on)
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_ON);
	else
		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);

	return 0;
}

static int lcd2s_fontsize(struct charlcd *lcd, enum charlcd_fontsize size)
{
	return 0;
}

static int lcd2s_lines(struct charlcd *lcd, enum charlcd_lines lines)
{
	return 0;
}

/*
 * Generator: LGcxxxxx...xx; must have <c> between '0' and '7',
 * representing the numerical ASCII code of the redefined character,
 * and <xx...xx> a sequence of 16 hex digits representing 8 bytes
 * for each character. Most LCDs will only use 5 lower bits of
 * the 7 first bytes.
 */
static int lcd2s_redefine_char(struct charlcd *lcd, char *esc)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;
	u8 buf[LCD2S_CHARACTER_SIZE + 2] = { LCD2S_CMD_DEF_CUSTOM_CHAR };
	u8 value;
	int shift, i;

	if (!strchr(esc, ';'))
		return 0;

	esc++;

	buf[1] = *(esc++) - '0';
	if (buf[1] > 7)
		return 1;

	i = 2;
	shift = 0;
	value = 0;
	while (*esc && i < LCD2S_CHARACTER_SIZE + 2) {
		int half;

		shift ^= 4;
		half = hex_to_bin(*esc++);
		if (half < 0)
			continue;

		value |= half << shift;
		if (shift == 0) {
			buf[i++] = value;
			value = 0;
		}
	}

	lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
	return 1;
}

static int lcd2s_clear_display(struct charlcd *lcd)
{
	struct lcd2s_data *lcd2s = lcd->drvdata;

	/* This implicitly sets cursor to first row and column */
	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
	return 0;
}

static const struct charlcd_ops lcd2s_ops = {
	.print		= lcd2s_print,
	.backlight	= lcd2s_backlight,
	.gotoxy		= lcd2s_gotoxy,
	.home		= lcd2s_home,
	.clear_display	= lcd2s_clear_display,
	.init_display	= lcd2s_init_display,
	.shift_cursor	= lcd2s_shift_cursor,
	.shift_display	= lcd2s_shift_display,
	.display	= lcd2s_display,
	.cursor		= lcd2s_cursor,
	.blink		= lcd2s_blink,
	.fontsize	= lcd2s_fontsize,
	.lines		= lcd2s_lines,
	.redefine_char	= lcd2s_redefine_char,
};

static int lcd2s_i2c_probe(struct i2c_client *i2c)
{
	struct charlcd *lcd;
	struct lcd2s_data *lcd2s;
	int err;

	if (!i2c_check_functionality(i2c->adapter,
			I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
			I2C_FUNC_SMBUS_WRITE_BLOCK_DATA))
		return -EIO;

	lcd2s = devm_kzalloc(&i2c->dev, sizeof(*lcd2s), GFP_KERNEL);
	if (!lcd2s)
		return -ENOMEM;

	/* Test, if the display is responding */
	err = lcd2s_i2c_smbus_write_byte(i2c, LCD2S_CMD_DISPLAY_OFF);
	if (err < 0)
		return err;

	lcd = charlcd_alloc();
	if (!lcd)
		return -ENOMEM;

	lcd->drvdata = lcd2s;
	lcd2s->i2c = i2c;
	lcd2s->charlcd = lcd;

	/* Required properties */
	err = device_property_read_u32(&i2c->dev, "display-height-chars",
			&lcd->height);
	if (err)
		goto fail1;

	err = device_property_read_u32(&i2c->dev, "display-width-chars",
			&lcd->width);
	if (err)
		goto fail1;

	lcd->ops = &lcd2s_ops;

	err = charlcd_register(lcd2s->charlcd);
	if (err)
		goto fail1;

	i2c_set_clientdata(i2c, lcd2s);
	return 0;

fail1:
	charlcd_free(lcd2s->charlcd);
	return err;
}

static int lcd2s_i2c_remove(struct i2c_client *i2c)
{
	struct lcd2s_data *lcd2s = i2c_get_clientdata(i2c);

	charlcd_unregister(lcd2s->charlcd);
	charlcd_free(lcd2s->charlcd);
	return 0;
}

static const struct i2c_device_id lcd2s_i2c_id[] = {
	{ "lcd2s", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, lcd2s_i2c_id);

static const struct of_device_id lcd2s_of_table[] = {
	{ .compatible = "modtronix,lcd2s" },
	{ }
};
MODULE_DEVICE_TABLE(of, lcd2s_of_table);

static struct i2c_driver lcd2s_i2c_driver = {
	.driver = {
		.name = "lcd2s",
		.of_match_table = lcd2s_of_table,
	},
	.probe_new = lcd2s_i2c_probe,
	.remove = lcd2s_i2c_remove,
	.id_table = lcd2s_i2c_id,
};
module_i2c_driver(lcd2s_i2c_driver);

MODULE_DESCRIPTION("LCD2S character display driver");
MODULE_AUTHOR("Lars Poeschel");
MODULE_LICENSE("GPL");