linux/drivers/i2c/busses/i2c-taos-evm.c
<<
>>
Prefs
   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");
 312