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