i2c-taos-evm.c (7540B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Driver for the TAOS evaluation modules 4 * These devices include an I2C master which can be controlled over the 5 * 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 63 20 21#define TAOS_STATE_INIT 0 22#define TAOS_STATE_IDLE 1 23#define TAOS_STATE_EOFF 2 24#define TAOS_STATE_RECV 3 25 26#define TAOS_CMD_RESET 0x12 27#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 the 67 SMBus command and "#" for the data. */ 68 p = taos->buffer; 69 70 /* The device remembers the last used address, no need to send it 71 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 else 80 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 else 86 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_IDLE 107 || 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 all 127 * error code that it could return are invalid according 128 * 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 strlcpy(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");