brintos

brintos / linux-shallow public Read only

0
0
Text · 7.4 KiB · cb97f72 Raw
312 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Driver for the TAOS evaluation modules4 * These devices include an I2C controller which can be controlled over the5 * serial port.6 *7 * Copyright (C) 2007 Jean Delvare <jdelvare@suse.de>8 */9 10#include <linux/delay.h>11#include <linux/module.h>12#include <linux/slab.h>13#include <linux/interrupt.h>14#include <linux/input.h>15#include <linux/serio.h>16#include <linux/init.h>17#include <linux/i2c.h>18 19#define TAOS_BUFFER_SIZE	6320 21#define TAOS_STATE_INIT		022#define TAOS_STATE_IDLE		123#define TAOS_STATE_EOFF		224#define TAOS_STATE_RECV		325 26#define TAOS_CMD_RESET		0x1227#define TAOS_CMD_ECHO_ON	'+'28#define TAOS_CMD_ECHO_OFF	'-'29 30static DECLARE_WAIT_QUEUE_HEAD(wq);31 32struct taos_data {33	struct i2c_adapter adapter;34	struct i2c_client *client;35	int state;36	u8 addr;		/* last used address */37	unsigned char buffer[TAOS_BUFFER_SIZE];38	unsigned int pos;	/* position inside the buffer */39};40 41/* TAOS TSL2550 EVM */42static const struct i2c_board_info tsl2550_info = {43	I2C_BOARD_INFO("tsl2550", 0x39),44};45 46/* Instantiate i2c devices based on the adapter name */47static struct i2c_client *taos_instantiate_device(struct i2c_adapter *adapter)48{49	if (!strncmp(adapter->name, "TAOS TSL2550 EVM", 16)) {50		dev_info(&adapter->dev, "Instantiating device %s at 0x%02x\n",51			tsl2550_info.type, tsl2550_info.addr);52		return i2c_new_client_device(adapter, &tsl2550_info);53	}54 55	return ERR_PTR(-ENODEV);56}57 58static int taos_smbus_xfer(struct i2c_adapter *adapter, u16 addr,59			   unsigned short flags, char read_write, u8 command,60			   int size, union i2c_smbus_data *data)61{62	struct serio *serio = adapter->algo_data;63	struct taos_data *taos = serio_get_drvdata(serio);64	char *p;65 66	/* Encode our transaction. "@" is for the device address, "$" for the67	   SMBus command and "#" for the data. */68	p = taos->buffer;69 70	/* The device remembers the last used address, no need to send it71	   again if it's the same */72	if (addr != taos->addr)73		p += sprintf(p, "@%02X", addr);74 75	switch (size) {76	case I2C_SMBUS_BYTE:77		if (read_write == I2C_SMBUS_WRITE)78			sprintf(p, "$#%02X", command);79		else80			sprintf(p, "$");81		break;82	case I2C_SMBUS_BYTE_DATA:83		if (read_write == I2C_SMBUS_WRITE)84			sprintf(p, "$%02X#%02X", command, data->byte);85		else86			sprintf(p, "$%02X", command);87		break;88	default:89		dev_warn(&adapter->dev, "Unsupported transaction %d\n", size);90		return -EOPNOTSUPP;91	}92 93	/* Send the transaction to the TAOS EVM */94	dev_dbg(&adapter->dev, "Command buffer: %s\n", taos->buffer);95	for (p = taos->buffer; *p; p++)96		serio_write(serio, *p);97 98	taos->addr = addr;99 100	/* Start the transaction and read the answer */101	taos->pos = 0;102	taos->state = TAOS_STATE_RECV;103	serio_write(serio, read_write == I2C_SMBUS_WRITE ? '>' : '<');104	wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE,105					 msecs_to_jiffies(150));106	if (taos->state != TAOS_STATE_IDLE107	 || taos->pos != 5) {108		dev_err(&adapter->dev, "Transaction timeout (pos=%d)\n",109			taos->pos);110		return -EIO;111	}112	dev_dbg(&adapter->dev, "Answer buffer: %s\n", taos->buffer);113 114	/* Interpret the returned string */115	p = taos->buffer + 1;116	p[3] = '\0';117	if (!strcmp(p, "NAK"))118		return -ENODEV;119 120	if (read_write == I2C_SMBUS_WRITE) {121		if (!strcmp(p, "ACK"))122			return 0;123	} else {124		if (p[0] == 'x') {125			/*126			 * Voluntarily dropping error code of kstrtou8 since all127			 * error code that it could return are invalid according128			 * to Documentation/i2c/fault-codes.rst.129			 */130			if (kstrtou8(p + 1, 16, &data->byte))131				return -EPROTO;132			return 0;133		}134	}135 136	return -EIO;137}138 139static u32 taos_smbus_func(struct i2c_adapter *adapter)140{141	return I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA;142}143 144static const struct i2c_algorithm taos_algorithm = {145	.smbus_xfer	= taos_smbus_xfer,146	.functionality	= taos_smbus_func,147};148 149static irqreturn_t taos_interrupt(struct serio *serio, unsigned char data,150				  unsigned int flags)151{152	struct taos_data *taos = serio_get_drvdata(serio);153 154	switch (taos->state) {155	case TAOS_STATE_INIT:156		taos->buffer[taos->pos++] = data;157		if (data == ':'158		 || taos->pos == TAOS_BUFFER_SIZE - 1) {159			taos->buffer[taos->pos] = '\0';160			taos->state = TAOS_STATE_IDLE;161			wake_up_interruptible(&wq);162		}163		break;164	case TAOS_STATE_EOFF:165		taos->state = TAOS_STATE_IDLE;166		wake_up_interruptible(&wq);167		break;168	case TAOS_STATE_RECV:169		taos->buffer[taos->pos++] = data;170		if (data == ']') {171			taos->buffer[taos->pos] = '\0';172			taos->state = TAOS_STATE_IDLE;173			wake_up_interruptible(&wq);174		}175		break;176	}177 178	return IRQ_HANDLED;179}180 181/* Extract the adapter name from the buffer received after reset.182   The buffer is modified and a pointer inside the buffer is returned. */183static char *taos_adapter_name(char *buffer)184{185	char *start, *end;186 187	start = strstr(buffer, "TAOS ");188	if (!start)189		return NULL;190 191	end = strchr(start, '\r');192	if (!end)193		return NULL;194	*end = '\0';195 196	return start;197}198 199static int taos_connect(struct serio *serio, struct serio_driver *drv)200{201	struct taos_data *taos;202	struct i2c_adapter *adapter;203	char *name;204	int err;205 206	taos = kzalloc(sizeof(struct taos_data), GFP_KERNEL);207	if (!taos) {208		err = -ENOMEM;209		goto exit;210	}211	taos->state = TAOS_STATE_INIT;212	serio_set_drvdata(serio, taos);213 214	err = serio_open(serio, drv);215	if (err)216		goto exit_kfree;217 218	adapter = &taos->adapter;219	adapter->owner = THIS_MODULE;220	adapter->algo = &taos_algorithm;221	adapter->algo_data = serio;222	adapter->dev.parent = &serio->dev;223 224	/* Reset the TAOS evaluation module to identify it */225	serio_write(serio, TAOS_CMD_RESET);226	wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE,227					 msecs_to_jiffies(2000));228 229	if (taos->state != TAOS_STATE_IDLE) {230		err = -ENODEV;231		dev_err(&serio->dev, "TAOS EVM reset failed (state=%d, "232			"pos=%d)\n", taos->state, taos->pos);233		goto exit_close;234	}235 236	name = taos_adapter_name(taos->buffer);237	if (!name) {238		err = -ENODEV;239		dev_err(&serio->dev, "TAOS EVM identification failed\n");240		goto exit_close;241	}242	strscpy(adapter->name, name, sizeof(adapter->name));243 244	/* Turn echo off for better performance */245	taos->state = TAOS_STATE_EOFF;246	serio_write(serio, TAOS_CMD_ECHO_OFF);247 248	wait_event_interruptible_timeout(wq, taos->state == TAOS_STATE_IDLE,249					 msecs_to_jiffies(250));250	if (taos->state != TAOS_STATE_IDLE) {251		err = -ENODEV;252		dev_err(&serio->dev, "TAOS EVM echo off failed "253			"(state=%d)\n", taos->state);254		goto exit_close;255	}256 257	err = i2c_add_adapter(adapter);258	if (err)259		goto exit_close;260	dev_info(&serio->dev, "Connected to TAOS EVM\n");261 262	taos->client = taos_instantiate_device(adapter);263	return 0;264 265 exit_close:266	serio_close(serio);267 exit_kfree:268	kfree(taos);269 exit:270	return err;271}272 273static void taos_disconnect(struct serio *serio)274{275	struct taos_data *taos = serio_get_drvdata(serio);276 277	i2c_unregister_device(taos->client);278	i2c_del_adapter(&taos->adapter);279	serio_close(serio);280	kfree(taos);281 282	dev_info(&serio->dev, "Disconnected from TAOS EVM\n");283}284 285static const struct serio_device_id taos_serio_ids[] = {286	{287		.type	= SERIO_RS232,288		.proto	= SERIO_TAOSEVM,289		.id	= SERIO_ANY,290		.extra	= SERIO_ANY,291	},292	{ 0 }293};294MODULE_DEVICE_TABLE(serio, taos_serio_ids);295 296static struct serio_driver taos_drv = {297	.driver		= {298		.name	= "taos-evm",299	},300	.description	= "TAOS evaluation module driver",301	.id_table	= taos_serio_ids,302	.connect	= taos_connect,303	.disconnect	= taos_disconnect,304	.interrupt	= taos_interrupt,305};306 307module_serio_driver(taos_drv);308 309MODULE_AUTHOR("Jean Delvare <jdelvare@suse.de>");310MODULE_DESCRIPTION("TAOS evaluation module driver");311MODULE_LICENSE("GPL");312