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