162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Console driver for LCD2S 4x20 character displays connected through i2c.
462306a36Sopenharmony_ci *  The display also has a SPI interface, but the driver does not support
562306a36Sopenharmony_ci *  this yet.
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci *  This is a driver allowing you to use a LCD2S 4x20 from Modtronix
862306a36Sopenharmony_ci *  engineering as auxdisplay character device.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci *  (C) 2019 by Lemonage Software GmbH
1162306a36Sopenharmony_ci *  Author: Lars Pöschel <poeschel@lemonage.de>
1262306a36Sopenharmony_ci *  All rights reserved.
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/property.h>
1862306a36Sopenharmony_ci#include <linux/slab.h>
1962306a36Sopenharmony_ci#include <linux/i2c.h>
2062306a36Sopenharmony_ci#include <linux/delay.h>
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci#include "charlcd.h"
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci#define LCD2S_CMD_CUR_MOVES_FWD		0x09
2562306a36Sopenharmony_ci#define LCD2S_CMD_CUR_BLINK_OFF		0x10
2662306a36Sopenharmony_ci#define LCD2S_CMD_CUR_UL_OFF		0x11
2762306a36Sopenharmony_ci#define LCD2S_CMD_DISPLAY_OFF		0x12
2862306a36Sopenharmony_ci#define LCD2S_CMD_CUR_BLINK_ON		0x18
2962306a36Sopenharmony_ci#define LCD2S_CMD_CUR_UL_ON		0x19
3062306a36Sopenharmony_ci#define LCD2S_CMD_DISPLAY_ON		0x1a
3162306a36Sopenharmony_ci#define LCD2S_CMD_BACKLIGHT_OFF		0x20
3262306a36Sopenharmony_ci#define LCD2S_CMD_BACKLIGHT_ON		0x28
3362306a36Sopenharmony_ci#define LCD2S_CMD_WRITE			0x80
3462306a36Sopenharmony_ci#define LCD2S_CMD_MOV_CUR_RIGHT		0x83
3562306a36Sopenharmony_ci#define LCD2S_CMD_MOV_CUR_LEFT		0x84
3662306a36Sopenharmony_ci#define LCD2S_CMD_SHIFT_RIGHT		0x85
3762306a36Sopenharmony_ci#define LCD2S_CMD_SHIFT_LEFT		0x86
3862306a36Sopenharmony_ci#define LCD2S_CMD_SHIFT_UP		0x87
3962306a36Sopenharmony_ci#define LCD2S_CMD_SHIFT_DOWN		0x88
4062306a36Sopenharmony_ci#define LCD2S_CMD_CUR_ADDR		0x89
4162306a36Sopenharmony_ci#define LCD2S_CMD_CUR_POS		0x8a
4262306a36Sopenharmony_ci#define LCD2S_CMD_CUR_RESET		0x8b
4362306a36Sopenharmony_ci#define LCD2S_CMD_CLEAR			0x8c
4462306a36Sopenharmony_ci#define LCD2S_CMD_DEF_CUSTOM_CHAR	0x92
4562306a36Sopenharmony_ci#define LCD2S_CMD_READ_STATUS		0xd0
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci#define LCD2S_CHARACTER_SIZE		8
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define LCD2S_STATUS_BUF_MASK		0x7f
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistruct lcd2s_data {
5262306a36Sopenharmony_ci	struct i2c_client *i2c;
5362306a36Sopenharmony_ci	struct charlcd *charlcd;
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic s32 lcd2s_wait_buf_free(const struct i2c_client *client, int count)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	s32 status;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	status = i2c_smbus_read_byte_data(client, LCD2S_CMD_READ_STATUS);
6162306a36Sopenharmony_ci	if (status < 0)
6262306a36Sopenharmony_ci		return status;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	while ((status & LCD2S_STATUS_BUF_MASK) < count) {
6562306a36Sopenharmony_ci		mdelay(1);
6662306a36Sopenharmony_ci		status = i2c_smbus_read_byte_data(client,
6762306a36Sopenharmony_ci						  LCD2S_CMD_READ_STATUS);
6862306a36Sopenharmony_ci		if (status < 0)
6962306a36Sopenharmony_ci			return status;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci	return 0;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic int lcd2s_i2c_master_send(const struct i2c_client *client,
7562306a36Sopenharmony_ci				 const char *buf, int count)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	s32 status;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	status = lcd2s_wait_buf_free(client, count);
8062306a36Sopenharmony_ci	if (status < 0)
8162306a36Sopenharmony_ci		return status;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	return i2c_master_send(client, buf, count);
8462306a36Sopenharmony_ci}
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic int lcd2s_i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	s32 status;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	status = lcd2s_wait_buf_free(client, 1);
9162306a36Sopenharmony_ci	if (status < 0)
9262306a36Sopenharmony_ci		return status;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return i2c_smbus_write_byte(client, value);
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic int lcd2s_print(struct charlcd *lcd, int c)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
10062306a36Sopenharmony_ci	u8 buf[2] = { LCD2S_CMD_WRITE, c };
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
10362306a36Sopenharmony_ci	return 0;
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic int lcd2s_gotoxy(struct charlcd *lcd, unsigned int x, unsigned int y)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
10962306a36Sopenharmony_ci	u8 buf[3] = { LCD2S_CMD_CUR_POS, y + 1, x + 1 };
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	return 0;
11462306a36Sopenharmony_ci}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic int lcd2s_home(struct charlcd *lcd)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_RESET);
12162306a36Sopenharmony_ci	return 0;
12262306a36Sopenharmony_ci}
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_cistatic int lcd2s_init_display(struct charlcd *lcd)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* turn everything off, but display on */
12962306a36Sopenharmony_ci	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
13062306a36Sopenharmony_ci	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
13162306a36Sopenharmony_ci	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_MOVES_FWD);
13262306a36Sopenharmony_ci	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
13362306a36Sopenharmony_ci	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
13462306a36Sopenharmony_ci	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_ci	return 0;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic int lcd2s_shift_cursor(struct charlcd *lcd, enum charlcd_shift_dir dir)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	if (dir == CHARLCD_SHIFT_LEFT)
14462306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_LEFT);
14562306a36Sopenharmony_ci	else
14662306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_MOV_CUR_RIGHT);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	return 0;
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic int lcd2s_shift_display(struct charlcd *lcd, enum charlcd_shift_dir dir)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	if (dir == CHARLCD_SHIFT_LEFT)
15662306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_LEFT);
15762306a36Sopenharmony_ci	else
15862306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_SHIFT_RIGHT);
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	return 0;
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic void lcd2s_backlight(struct charlcd *lcd, enum charlcd_onoff on)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	if (on)
16862306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_ON);
16962306a36Sopenharmony_ci	else
17062306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_BACKLIGHT_OFF);
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_cistatic int lcd2s_display(struct charlcd *lcd, enum charlcd_onoff on)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	if (on)
17862306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_ON);
17962306a36Sopenharmony_ci	else
18062306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_DISPLAY_OFF);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return 0;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic int lcd2s_cursor(struct charlcd *lcd, enum charlcd_onoff on)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	if (on)
19062306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_ON);
19162306a36Sopenharmony_ci	else
19262306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_UL_OFF);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	return 0;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic int lcd2s_blink(struct charlcd *lcd, enum charlcd_onoff on)
19862306a36Sopenharmony_ci{
19962306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	if (on)
20262306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_ON);
20362306a36Sopenharmony_ci	else
20462306a36Sopenharmony_ci		lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CUR_BLINK_OFF);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	return 0;
20762306a36Sopenharmony_ci}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistatic int lcd2s_fontsize(struct charlcd *lcd, enum charlcd_fontsize size)
21062306a36Sopenharmony_ci{
21162306a36Sopenharmony_ci	return 0;
21262306a36Sopenharmony_ci}
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_cistatic int lcd2s_lines(struct charlcd *lcd, enum charlcd_lines lines)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	return 0;
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci/*
22062306a36Sopenharmony_ci * Generator: LGcxxxxx...xx; must have <c> between '0' and '7',
22162306a36Sopenharmony_ci * representing the numerical ASCII code of the redefined character,
22262306a36Sopenharmony_ci * and <xx...xx> a sequence of 16 hex digits representing 8 bytes
22362306a36Sopenharmony_ci * for each character. Most LCDs will only use 5 lower bits of
22462306a36Sopenharmony_ci * the 7 first bytes.
22562306a36Sopenharmony_ci */
22662306a36Sopenharmony_cistatic int lcd2s_redefine_char(struct charlcd *lcd, char *esc)
22762306a36Sopenharmony_ci{
22862306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
22962306a36Sopenharmony_ci	u8 buf[LCD2S_CHARACTER_SIZE + 2] = { LCD2S_CMD_DEF_CUSTOM_CHAR };
23062306a36Sopenharmony_ci	u8 value;
23162306a36Sopenharmony_ci	int shift, i;
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	if (!strchr(esc, ';'))
23462306a36Sopenharmony_ci		return 0;
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	esc++;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	buf[1] = *(esc++) - '0';
23962306a36Sopenharmony_ci	if (buf[1] > 7)
24062306a36Sopenharmony_ci		return 1;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	i = 2;
24362306a36Sopenharmony_ci	shift = 0;
24462306a36Sopenharmony_ci	value = 0;
24562306a36Sopenharmony_ci	while (*esc && i < LCD2S_CHARACTER_SIZE + 2) {
24662306a36Sopenharmony_ci		int half;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci		shift ^= 4;
24962306a36Sopenharmony_ci		half = hex_to_bin(*esc++);
25062306a36Sopenharmony_ci		if (half < 0)
25162306a36Sopenharmony_ci			continue;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci		value |= half << shift;
25462306a36Sopenharmony_ci		if (shift == 0) {
25562306a36Sopenharmony_ci			buf[i++] = value;
25662306a36Sopenharmony_ci			value = 0;
25762306a36Sopenharmony_ci		}
25862306a36Sopenharmony_ci	}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	lcd2s_i2c_master_send(lcd2s->i2c, buf, sizeof(buf));
26162306a36Sopenharmony_ci	return 1;
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic int lcd2s_clear_display(struct charlcd *lcd)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = lcd->drvdata;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	/* This implicitly sets cursor to first row and column */
26962306a36Sopenharmony_ci	lcd2s_i2c_smbus_write_byte(lcd2s->i2c, LCD2S_CMD_CLEAR);
27062306a36Sopenharmony_ci	return 0;
27162306a36Sopenharmony_ci}
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_cistatic const struct charlcd_ops lcd2s_ops = {
27462306a36Sopenharmony_ci	.print		= lcd2s_print,
27562306a36Sopenharmony_ci	.backlight	= lcd2s_backlight,
27662306a36Sopenharmony_ci	.gotoxy		= lcd2s_gotoxy,
27762306a36Sopenharmony_ci	.home		= lcd2s_home,
27862306a36Sopenharmony_ci	.clear_display	= lcd2s_clear_display,
27962306a36Sopenharmony_ci	.init_display	= lcd2s_init_display,
28062306a36Sopenharmony_ci	.shift_cursor	= lcd2s_shift_cursor,
28162306a36Sopenharmony_ci	.shift_display	= lcd2s_shift_display,
28262306a36Sopenharmony_ci	.display	= lcd2s_display,
28362306a36Sopenharmony_ci	.cursor		= lcd2s_cursor,
28462306a36Sopenharmony_ci	.blink		= lcd2s_blink,
28562306a36Sopenharmony_ci	.fontsize	= lcd2s_fontsize,
28662306a36Sopenharmony_ci	.lines		= lcd2s_lines,
28762306a36Sopenharmony_ci	.redefine_char	= lcd2s_redefine_char,
28862306a36Sopenharmony_ci};
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_cistatic int lcd2s_i2c_probe(struct i2c_client *i2c)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	struct charlcd *lcd;
29362306a36Sopenharmony_ci	struct lcd2s_data *lcd2s;
29462306a36Sopenharmony_ci	int err;
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	if (!i2c_check_functionality(i2c->adapter,
29762306a36Sopenharmony_ci			I2C_FUNC_SMBUS_WRITE_BYTE_DATA |
29862306a36Sopenharmony_ci			I2C_FUNC_SMBUS_WRITE_BLOCK_DATA))
29962306a36Sopenharmony_ci		return -EIO;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	lcd2s = devm_kzalloc(&i2c->dev, sizeof(*lcd2s), GFP_KERNEL);
30262306a36Sopenharmony_ci	if (!lcd2s)
30362306a36Sopenharmony_ci		return -ENOMEM;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	/* Test, if the display is responding */
30662306a36Sopenharmony_ci	err = lcd2s_i2c_smbus_write_byte(i2c, LCD2S_CMD_DISPLAY_OFF);
30762306a36Sopenharmony_ci	if (err < 0)
30862306a36Sopenharmony_ci		return err;
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ci	lcd = charlcd_alloc();
31162306a36Sopenharmony_ci	if (!lcd)
31262306a36Sopenharmony_ci		return -ENOMEM;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	lcd->drvdata = lcd2s;
31562306a36Sopenharmony_ci	lcd2s->i2c = i2c;
31662306a36Sopenharmony_ci	lcd2s->charlcd = lcd;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	/* Required properties */
31962306a36Sopenharmony_ci	err = device_property_read_u32(&i2c->dev, "display-height-chars",
32062306a36Sopenharmony_ci			&lcd->height);
32162306a36Sopenharmony_ci	if (err)
32262306a36Sopenharmony_ci		goto fail1;
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci	err = device_property_read_u32(&i2c->dev, "display-width-chars",
32562306a36Sopenharmony_ci			&lcd->width);
32662306a36Sopenharmony_ci	if (err)
32762306a36Sopenharmony_ci		goto fail1;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	lcd->ops = &lcd2s_ops;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	err = charlcd_register(lcd2s->charlcd);
33262306a36Sopenharmony_ci	if (err)
33362306a36Sopenharmony_ci		goto fail1;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	i2c_set_clientdata(i2c, lcd2s);
33662306a36Sopenharmony_ci	return 0;
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_cifail1:
33962306a36Sopenharmony_ci	charlcd_free(lcd2s->charlcd);
34062306a36Sopenharmony_ci	return err;
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_cistatic void lcd2s_i2c_remove(struct i2c_client *i2c)
34462306a36Sopenharmony_ci{
34562306a36Sopenharmony_ci	struct lcd2s_data *lcd2s = i2c_get_clientdata(i2c);
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	charlcd_unregister(lcd2s->charlcd);
34862306a36Sopenharmony_ci	charlcd_free(lcd2s->charlcd);
34962306a36Sopenharmony_ci}
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_cistatic const struct i2c_device_id lcd2s_i2c_id[] = {
35262306a36Sopenharmony_ci	{ "lcd2s", 0 },
35362306a36Sopenharmony_ci	{ }
35462306a36Sopenharmony_ci};
35562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, lcd2s_i2c_id);
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_cistatic const struct of_device_id lcd2s_of_table[] = {
35862306a36Sopenharmony_ci	{ .compatible = "modtronix,lcd2s" },
35962306a36Sopenharmony_ci	{ }
36062306a36Sopenharmony_ci};
36162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, lcd2s_of_table);
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_cistatic struct i2c_driver lcd2s_i2c_driver = {
36462306a36Sopenharmony_ci	.driver = {
36562306a36Sopenharmony_ci		.name = "lcd2s",
36662306a36Sopenharmony_ci		.of_match_table = lcd2s_of_table,
36762306a36Sopenharmony_ci	},
36862306a36Sopenharmony_ci	.probe = lcd2s_i2c_probe,
36962306a36Sopenharmony_ci	.remove = lcd2s_i2c_remove,
37062306a36Sopenharmony_ci	.id_table = lcd2s_i2c_id,
37162306a36Sopenharmony_ci};
37262306a36Sopenharmony_cimodule_i2c_driver(lcd2s_i2c_driver);
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ciMODULE_DESCRIPTION("LCD2S character display driver");
37562306a36Sopenharmony_ciMODULE_AUTHOR("Lars Poeschel");
37662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
377