18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for the TAOS evaluation modules
48c2ecf20Sopenharmony_ci * These devices include an I2C master which can be controlled over the
58c2ecf20Sopenharmony_ci * serial port.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (C) 2007 Jean Delvare <jdelvare@suse.de>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/delay.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
148c2ecf20Sopenharmony_ci#include <linux/input.h>
158c2ecf20Sopenharmony_ci#include <linux/serio.h>
168c2ecf20Sopenharmony_ci#include <linux/init.h>
178c2ecf20Sopenharmony_ci#include <linux/i2c.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define TAOS_BUFFER_SIZE	63
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define TAOS_STATE_INIT		0
228c2ecf20Sopenharmony_ci#define TAOS_STATE_IDLE		1
238c2ecf20Sopenharmony_ci#define TAOS_STATE_EOFF		2
248c2ecf20Sopenharmony_ci#define TAOS_STATE_RECV		3
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#define TAOS_CMD_RESET		0x12
278c2ecf20Sopenharmony_ci#define TAOS_CMD_ECHO_ON	'+'
288c2ecf20Sopenharmony_ci#define TAOS_CMD_ECHO_OFF	'-'
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(wq);
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistruct taos_data {
338c2ecf20Sopenharmony_ci	struct i2c_adapter adapter;
348c2ecf20Sopenharmony_ci	struct i2c_client *client;
358c2ecf20Sopenharmony_ci	int state;
368c2ecf20Sopenharmony_ci	u8 addr;		/* last used address */
378c2ecf20Sopenharmony_ci	unsigned char buffer[TAOS_BUFFER_SIZE];
388c2ecf20Sopenharmony_ci	unsigned int pos;	/* position inside the buffer */
398c2ecf20Sopenharmony_ci};
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci/* TAOS TSL2550 EVM */
428c2ecf20Sopenharmony_cistatic const struct i2c_board_info tsl2550_info = {
438c2ecf20Sopenharmony_ci	I2C_BOARD_INFO("tsl2550", 0x39),
448c2ecf20Sopenharmony_ci};
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci/* Instantiate i2c devices based on the adapter name */
478c2ecf20Sopenharmony_cistatic struct i2c_client *taos_instantiate_device(struct i2c_adapter *adapter)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	if (!strncmp(adapter->name, "TAOS TSL2550 EVM", 16)) {
508c2ecf20Sopenharmony_ci		dev_info(&adapter->dev, "Instantiating device %s at 0x%02x\n",
518c2ecf20Sopenharmony_ci			tsl2550_info.type, tsl2550_info.addr);
528c2ecf20Sopenharmony_ci		return i2c_new_client_device(adapter, &tsl2550_info);
538c2ecf20Sopenharmony_ci	}
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci	return ERR_PTR(-ENODEV);
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistatic int taos_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
598c2ecf20Sopenharmony_ci			   unsigned short flags, char read_write, u8 command,
608c2ecf20Sopenharmony_ci			   int size, union i2c_smbus_data *data)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct serio *serio = adapter->algo_data;
638c2ecf20Sopenharmony_ci	struct taos_data *taos = serio_get_drvdata(serio);
648c2ecf20Sopenharmony_ci	char *p;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	/* Encode our transaction. "@" is for the device address, "$" for the
678c2ecf20Sopenharmony_ci	   SMBus command and "#" for the data. */
688c2ecf20Sopenharmony_ci	p = taos->buffer;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	/* The device remembers the last used address, no need to send it
718c2ecf20Sopenharmony_ci	   again if it's the same */
728c2ecf20Sopenharmony_ci	if (addr != taos->addr)
738c2ecf20Sopenharmony_ci		p += sprintf(p, "@%02X", addr);
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	switch (size) {
768c2ecf20Sopenharmony_ci	case I2C_SMBUS_BYTE:
778c2ecf20Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE)
788c2ecf20Sopenharmony_ci			sprintf(p, "$#%02X", command);
798c2ecf20Sopenharmony_ci		else
808c2ecf20Sopenharmony_ci			sprintf(p, "$");
818c2ecf20Sopenharmony_ci		break;
828c2ecf20Sopenharmony_ci	case I2C_SMBUS_BYTE_DATA:
838c2ecf20Sopenharmony_ci		if (read_write == I2C_SMBUS_WRITE)
848c2ecf20Sopenharmony_ci			sprintf(p, "$%02X#%02X", command, data->byte);
858c2ecf20Sopenharmony_ci		else
868c2ecf20Sopenharmony_ci			sprintf(p, "$%02X", command);
878c2ecf20Sopenharmony_ci		break;
888c2ecf20Sopenharmony_ci	default:
898c2ecf20Sopenharmony_ci		dev_warn(&adapter->dev, "Unsupported transaction %d\n", size);
908c2ecf20Sopenharmony_ci		return -EOPNOTSUPP;
918c2ecf20Sopenharmony_ci	}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	/* Send the transaction to the TAOS EVM */
948c2ecf20Sopenharmony_ci	dev_dbg(&adapter->dev, "Command buffer: %s\n", taos->buffer);
958c2ecf20Sopenharmony_ci	for (p = taos->buffer; *p; p++)
968c2ecf20Sopenharmony_ci		serio_write(serio, *p);
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	taos->addr = addr;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	/* Start the transaction and read the answer */
1018c2ecf20Sopenharmony_ci	taos->pos = 0;
1028c2ecf20Sopenharmony_ci	taos->state = TAOS_STATE_RECV;
1038c2ecf20Sopenharmony_ci	serio_write(serio, read_write == I2C_SMBUS_WRITE ? '>' : '<');
1048c2ecf20Sopenharmony_ci	wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE,
1058c2ecf20Sopenharmony_ci					 msecs_to_jiffies(150));
1068c2ecf20Sopenharmony_ci	if (taos->state != TAOS_STATE_IDLE
1078c2ecf20Sopenharmony_ci	 || taos->pos != 5) {
1088c2ecf20Sopenharmony_ci		dev_err(&adapter->dev, "Transaction timeout (pos=%d)\n",
1098c2ecf20Sopenharmony_ci			taos->pos);
1108c2ecf20Sopenharmony_ci		return -EIO;
1118c2ecf20Sopenharmony_ci	}
1128c2ecf20Sopenharmony_ci	dev_dbg(&adapter->dev, "Answer buffer: %s\n", taos->buffer);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	/* Interpret the returned string */
1158c2ecf20Sopenharmony_ci	p = taos->buffer + 1;
1168c2ecf20Sopenharmony_ci	p[3] = '\0';
1178c2ecf20Sopenharmony_ci	if (!strcmp(p, "NAK"))
1188c2ecf20Sopenharmony_ci		return -ENODEV;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	if (read_write == I2C_SMBUS_WRITE) {
1218c2ecf20Sopenharmony_ci		if (!strcmp(p, "ACK"))
1228c2ecf20Sopenharmony_ci			return 0;
1238c2ecf20Sopenharmony_ci	} else {
1248c2ecf20Sopenharmony_ci		if (p[0] == 'x') {
1258c2ecf20Sopenharmony_ci			/*
1268c2ecf20Sopenharmony_ci			 * Voluntarily dropping error code of kstrtou8 since all
1278c2ecf20Sopenharmony_ci			 * error code that it could return are invalid according
1288c2ecf20Sopenharmony_ci			 * to Documentation/i2c/fault-codes.rst.
1298c2ecf20Sopenharmony_ci			 */
1308c2ecf20Sopenharmony_ci			if (kstrtou8(p + 1, 16, &data->byte))
1318c2ecf20Sopenharmony_ci				return -EPROTO;
1328c2ecf20Sopenharmony_ci			return 0;
1338c2ecf20Sopenharmony_ci		}
1348c2ecf20Sopenharmony_ci	}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	return -EIO;
1378c2ecf20Sopenharmony_ci}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_cistatic u32 taos_smbus_func(struct i2c_adapter *adapter)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA;
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic const struct i2c_algorithm taos_algorithm = {
1458c2ecf20Sopenharmony_ci	.smbus_xfer	= taos_smbus_xfer,
1468c2ecf20Sopenharmony_ci	.functionality	= taos_smbus_func,
1478c2ecf20Sopenharmony_ci};
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic irqreturn_t taos_interrupt(struct serio *serio, unsigned char data,
1508c2ecf20Sopenharmony_ci				  unsigned int flags)
1518c2ecf20Sopenharmony_ci{
1528c2ecf20Sopenharmony_ci	struct taos_data *taos = serio_get_drvdata(serio);
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	switch (taos->state) {
1558c2ecf20Sopenharmony_ci	case TAOS_STATE_INIT:
1568c2ecf20Sopenharmony_ci		taos->buffer[taos->pos++] = data;
1578c2ecf20Sopenharmony_ci		if (data == ':'
1588c2ecf20Sopenharmony_ci		 || taos->pos == TAOS_BUFFER_SIZE - 1) {
1598c2ecf20Sopenharmony_ci			taos->buffer[taos->pos] = '\0';
1608c2ecf20Sopenharmony_ci			taos->state = TAOS_STATE_IDLE;
1618c2ecf20Sopenharmony_ci			wake_up_interruptible(&wq);
1628c2ecf20Sopenharmony_ci		}
1638c2ecf20Sopenharmony_ci		break;
1648c2ecf20Sopenharmony_ci	case TAOS_STATE_EOFF:
1658c2ecf20Sopenharmony_ci		taos->state = TAOS_STATE_IDLE;
1668c2ecf20Sopenharmony_ci		wake_up_interruptible(&wq);
1678c2ecf20Sopenharmony_ci		break;
1688c2ecf20Sopenharmony_ci	case TAOS_STATE_RECV:
1698c2ecf20Sopenharmony_ci		taos->buffer[taos->pos++] = data;
1708c2ecf20Sopenharmony_ci		if (data == ']') {
1718c2ecf20Sopenharmony_ci			taos->buffer[taos->pos] = '\0';
1728c2ecf20Sopenharmony_ci			taos->state = TAOS_STATE_IDLE;
1738c2ecf20Sopenharmony_ci			wake_up_interruptible(&wq);
1748c2ecf20Sopenharmony_ci		}
1758c2ecf20Sopenharmony_ci		break;
1768c2ecf20Sopenharmony_ci	}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1798c2ecf20Sopenharmony_ci}
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci/* Extract the adapter name from the buffer received after reset.
1828c2ecf20Sopenharmony_ci   The buffer is modified and a pointer inside the buffer is returned. */
1838c2ecf20Sopenharmony_cistatic char *taos_adapter_name(char *buffer)
1848c2ecf20Sopenharmony_ci{
1858c2ecf20Sopenharmony_ci	char *start, *end;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	start = strstr(buffer, "TAOS ");
1888c2ecf20Sopenharmony_ci	if (!start)
1898c2ecf20Sopenharmony_ci		return NULL;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	end = strchr(start, '\r');
1928c2ecf20Sopenharmony_ci	if (!end)
1938c2ecf20Sopenharmony_ci		return NULL;
1948c2ecf20Sopenharmony_ci	*end = '\0';
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	return start;
1978c2ecf20Sopenharmony_ci}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic int taos_connect(struct serio *serio, struct serio_driver *drv)
2008c2ecf20Sopenharmony_ci{
2018c2ecf20Sopenharmony_ci	struct taos_data *taos;
2028c2ecf20Sopenharmony_ci	struct i2c_adapter *adapter;
2038c2ecf20Sopenharmony_ci	char *name;
2048c2ecf20Sopenharmony_ci	int err;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	taos = kzalloc(sizeof(struct taos_data), GFP_KERNEL);
2078c2ecf20Sopenharmony_ci	if (!taos) {
2088c2ecf20Sopenharmony_ci		err = -ENOMEM;
2098c2ecf20Sopenharmony_ci		goto exit;
2108c2ecf20Sopenharmony_ci	}
2118c2ecf20Sopenharmony_ci	taos->state = TAOS_STATE_INIT;
2128c2ecf20Sopenharmony_ci	serio_set_drvdata(serio, taos);
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	err = serio_open(serio, drv);
2158c2ecf20Sopenharmony_ci	if (err)
2168c2ecf20Sopenharmony_ci		goto exit_kfree;
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	adapter = &taos->adapter;
2198c2ecf20Sopenharmony_ci	adapter->owner = THIS_MODULE;
2208c2ecf20Sopenharmony_ci	adapter->algo = &taos_algorithm;
2218c2ecf20Sopenharmony_ci	adapter->algo_data = serio;
2228c2ecf20Sopenharmony_ci	adapter->dev.parent = &serio->dev;
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci	/* Reset the TAOS evaluation module to identify it */
2258c2ecf20Sopenharmony_ci	serio_write(serio, TAOS_CMD_RESET);
2268c2ecf20Sopenharmony_ci	wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE,
2278c2ecf20Sopenharmony_ci					 msecs_to_jiffies(2000));
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	if (taos->state != TAOS_STATE_IDLE) {
2308c2ecf20Sopenharmony_ci		err = -ENODEV;
2318c2ecf20Sopenharmony_ci		dev_err(&serio->dev, "TAOS EVM reset failed (state=%d, "
2328c2ecf20Sopenharmony_ci			"pos=%d)\n", taos->state, taos->pos);
2338c2ecf20Sopenharmony_ci		goto exit_close;
2348c2ecf20Sopenharmony_ci	}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	name = taos_adapter_name(taos->buffer);
2378c2ecf20Sopenharmony_ci	if (!name) {
2388c2ecf20Sopenharmony_ci		err = -ENODEV;
2398c2ecf20Sopenharmony_ci		dev_err(&serio->dev, "TAOS EVM identification failed\n");
2408c2ecf20Sopenharmony_ci		goto exit_close;
2418c2ecf20Sopenharmony_ci	}
2428c2ecf20Sopenharmony_ci	strlcpy(adapter->name, name, sizeof(adapter->name));
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci	/* Turn echo off for better performance */
2458c2ecf20Sopenharmony_ci	taos->state = TAOS_STATE_EOFF;
2468c2ecf20Sopenharmony_ci	serio_write(serio, TAOS_CMD_ECHO_OFF);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE,
2498c2ecf20Sopenharmony_ci					 msecs_to_jiffies(250));
2508c2ecf20Sopenharmony_ci	if (taos->state != TAOS_STATE_IDLE) {
2518c2ecf20Sopenharmony_ci		err = -ENODEV;
2528c2ecf20Sopenharmony_ci		dev_err(&serio->dev, "TAOS EVM echo off failed "
2538c2ecf20Sopenharmony_ci			"(state=%d)\n", taos->state);
2548c2ecf20Sopenharmony_ci		goto exit_close;
2558c2ecf20Sopenharmony_ci	}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	err = i2c_add_adapter(adapter);
2588c2ecf20Sopenharmony_ci	if (err)
2598c2ecf20Sopenharmony_ci		goto exit_close;
2608c2ecf20Sopenharmony_ci	dev_info(&serio->dev, "Connected to TAOS EVM\n");
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	taos->client = taos_instantiate_device(adapter);
2638c2ecf20Sopenharmony_ci	return 0;
2648c2ecf20Sopenharmony_ci
2658c2ecf20Sopenharmony_ci exit_close:
2668c2ecf20Sopenharmony_ci	serio_close(serio);
2678c2ecf20Sopenharmony_ci exit_kfree:
2688c2ecf20Sopenharmony_ci	kfree(taos);
2698c2ecf20Sopenharmony_ci exit:
2708c2ecf20Sopenharmony_ci	return err;
2718c2ecf20Sopenharmony_ci}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_cistatic void taos_disconnect(struct serio *serio)
2748c2ecf20Sopenharmony_ci{
2758c2ecf20Sopenharmony_ci	struct taos_data *taos = serio_get_drvdata(serio);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	i2c_unregister_device(taos->client);
2788c2ecf20Sopenharmony_ci	i2c_del_adapter(&taos->adapter);
2798c2ecf20Sopenharmony_ci	serio_close(serio);
2808c2ecf20Sopenharmony_ci	kfree(taos);
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	dev_info(&serio->dev, "Disconnected from TAOS EVM\n");
2838c2ecf20Sopenharmony_ci}
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_cistatic const struct serio_device_id taos_serio_ids[] = {
2868c2ecf20Sopenharmony_ci	{
2878c2ecf20Sopenharmony_ci		.type	= SERIO_RS232,
2888c2ecf20Sopenharmony_ci		.proto	= SERIO_TAOSEVM,
2898c2ecf20Sopenharmony_ci		.id	= SERIO_ANY,
2908c2ecf20Sopenharmony_ci		.extra	= SERIO_ANY,
2918c2ecf20Sopenharmony_ci	},
2928c2ecf20Sopenharmony_ci	{ 0 }
2938c2ecf20Sopenharmony_ci};
2948c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(serio, taos_serio_ids);
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_cistatic struct serio_driver taos_drv = {
2978c2ecf20Sopenharmony_ci	.driver		= {
2988c2ecf20Sopenharmony_ci		.name	= "taos-evm",
2998c2ecf20Sopenharmony_ci	},
3008c2ecf20Sopenharmony_ci	.description	= "TAOS evaluation module driver",
3018c2ecf20Sopenharmony_ci	.id_table	= taos_serio_ids,
3028c2ecf20Sopenharmony_ci	.connect	= taos_connect,
3038c2ecf20Sopenharmony_ci	.disconnect	= taos_disconnect,
3048c2ecf20Sopenharmony_ci	.interrupt	= taos_interrupt,
3058c2ecf20Sopenharmony_ci};
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_cimodule_serio_driver(taos_drv);
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jean Delvare <jdelvare@suse.de>");
3108c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("TAOS evaluation module driver");
3118c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
312