162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * I2C slave mode testunit 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (C) 2020 by Wolfram Sang, Sang Engineering <wsa@sang-engineering.com> 662306a36Sopenharmony_ci * Copyright (C) 2020 by Renesas Electronics Corporation 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/bitops.h> 1062306a36Sopenharmony_ci#include <linux/i2c.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci#include <linux/workqueue.h> /* FIXME: is system_long_wq the best choice? */ 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_ci#define TU_CUR_VERSION 0x01 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_cienum testunit_cmds { 2062306a36Sopenharmony_ci TU_CMD_READ_BYTES = 1, /* save 0 for ABORT, RESET or similar */ 2162306a36Sopenharmony_ci TU_CMD_HOST_NOTIFY, 2262306a36Sopenharmony_ci TU_CMD_SMBUS_BLOCK_PROC_CALL, 2362306a36Sopenharmony_ci TU_NUM_CMDS 2462306a36Sopenharmony_ci}; 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_cienum testunit_regs { 2762306a36Sopenharmony_ci TU_REG_CMD, 2862306a36Sopenharmony_ci TU_REG_DATAL, 2962306a36Sopenharmony_ci TU_REG_DATAH, 3062306a36Sopenharmony_ci TU_REG_DELAY, 3162306a36Sopenharmony_ci TU_NUM_REGS 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cienum testunit_flags { 3562306a36Sopenharmony_ci TU_FLAG_IN_PROCESS, 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistruct testunit_data { 3962306a36Sopenharmony_ci unsigned long flags; 4062306a36Sopenharmony_ci u8 regs[TU_NUM_REGS]; 4162306a36Sopenharmony_ci u8 reg_idx; 4262306a36Sopenharmony_ci struct i2c_client *client; 4362306a36Sopenharmony_ci struct delayed_work worker; 4462306a36Sopenharmony_ci}; 4562306a36Sopenharmony_ci 4662306a36Sopenharmony_cistatic void i2c_slave_testunit_work(struct work_struct *work) 4762306a36Sopenharmony_ci{ 4862306a36Sopenharmony_ci struct testunit_data *tu = container_of(work, struct testunit_data, worker.work); 4962306a36Sopenharmony_ci struct i2c_msg msg; 5062306a36Sopenharmony_ci u8 msgbuf[256]; 5162306a36Sopenharmony_ci int ret = 0; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci msg.addr = I2C_CLIENT_END; 5462306a36Sopenharmony_ci msg.buf = msgbuf; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci switch (tu->regs[TU_REG_CMD]) { 5762306a36Sopenharmony_ci case TU_CMD_READ_BYTES: 5862306a36Sopenharmony_ci msg.addr = tu->regs[TU_REG_DATAL]; 5962306a36Sopenharmony_ci msg.flags = I2C_M_RD; 6062306a36Sopenharmony_ci msg.len = tu->regs[TU_REG_DATAH]; 6162306a36Sopenharmony_ci break; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci case TU_CMD_HOST_NOTIFY: 6462306a36Sopenharmony_ci msg.addr = 0x08; 6562306a36Sopenharmony_ci msg.flags = 0; 6662306a36Sopenharmony_ci msg.len = 3; 6762306a36Sopenharmony_ci msgbuf[0] = tu->client->addr; 6862306a36Sopenharmony_ci msgbuf[1] = tu->regs[TU_REG_DATAL]; 6962306a36Sopenharmony_ci msgbuf[2] = tu->regs[TU_REG_DATAH]; 7062306a36Sopenharmony_ci break; 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci default: 7362306a36Sopenharmony_ci break; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (msg.addr != I2C_CLIENT_END) { 7762306a36Sopenharmony_ci ret = i2c_transfer(tu->client->adapter, &msg, 1); 7862306a36Sopenharmony_ci /* convert '0 msgs transferred' to errno */ 7962306a36Sopenharmony_ci ret = (ret == 0) ? -EIO : ret; 8062306a36Sopenharmony_ci } 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci if (ret < 0) 8362306a36Sopenharmony_ci dev_err(&tu->client->dev, "CMD%02X failed (%d)\n", tu->regs[TU_REG_CMD], ret); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci clear_bit(TU_FLAG_IN_PROCESS, &tu->flags); 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic int i2c_slave_testunit_slave_cb(struct i2c_client *client, 8962306a36Sopenharmony_ci enum i2c_slave_event event, u8 *val) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct testunit_data *tu = i2c_get_clientdata(client); 9262306a36Sopenharmony_ci bool is_proc_call = tu->reg_idx == 3 && tu->regs[TU_REG_DATAL] == 1 && 9362306a36Sopenharmony_ci tu->regs[TU_REG_CMD] == TU_CMD_SMBUS_BLOCK_PROC_CALL; 9462306a36Sopenharmony_ci int ret = 0; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci switch (event) { 9762306a36Sopenharmony_ci case I2C_SLAVE_WRITE_RECEIVED: 9862306a36Sopenharmony_ci if (test_bit(TU_FLAG_IN_PROCESS, &tu->flags)) 9962306a36Sopenharmony_ci return -EBUSY; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci if (tu->reg_idx < TU_NUM_REGS) 10262306a36Sopenharmony_ci tu->regs[tu->reg_idx] = *val; 10362306a36Sopenharmony_ci else 10462306a36Sopenharmony_ci ret = -EMSGSIZE; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci if (tu->reg_idx <= TU_NUM_REGS) 10762306a36Sopenharmony_ci tu->reg_idx++; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci /* TU_REG_CMD always written at this point */ 11062306a36Sopenharmony_ci if (tu->regs[TU_REG_CMD] >= TU_NUM_CMDS) 11162306a36Sopenharmony_ci ret = -EINVAL; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci break; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci case I2C_SLAVE_STOP: 11662306a36Sopenharmony_ci if (tu->reg_idx == TU_NUM_REGS) { 11762306a36Sopenharmony_ci set_bit(TU_FLAG_IN_PROCESS, &tu->flags); 11862306a36Sopenharmony_ci queue_delayed_work(system_long_wq, &tu->worker, 11962306a36Sopenharmony_ci msecs_to_jiffies(10 * tu->regs[TU_REG_DELAY])); 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci fallthrough; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci case I2C_SLAVE_WRITE_REQUESTED: 12462306a36Sopenharmony_ci memset(tu->regs, 0, TU_NUM_REGS); 12562306a36Sopenharmony_ci tu->reg_idx = 0; 12662306a36Sopenharmony_ci break; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci case I2C_SLAVE_READ_PROCESSED: 12962306a36Sopenharmony_ci if (is_proc_call && tu->regs[TU_REG_DATAH]) 13062306a36Sopenharmony_ci tu->regs[TU_REG_DATAH]--; 13162306a36Sopenharmony_ci fallthrough; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci case I2C_SLAVE_READ_REQUESTED: 13462306a36Sopenharmony_ci *val = is_proc_call ? tu->regs[TU_REG_DATAH] : TU_CUR_VERSION; 13562306a36Sopenharmony_ci break; 13662306a36Sopenharmony_ci } 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return ret; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic int i2c_slave_testunit_probe(struct i2c_client *client) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci struct testunit_data *tu; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci tu = devm_kzalloc(&client->dev, sizeof(struct testunit_data), GFP_KERNEL); 14662306a36Sopenharmony_ci if (!tu) 14762306a36Sopenharmony_ci return -ENOMEM; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci tu->client = client; 15062306a36Sopenharmony_ci i2c_set_clientdata(client, tu); 15162306a36Sopenharmony_ci INIT_DELAYED_WORK(&tu->worker, i2c_slave_testunit_work); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return i2c_slave_register(client, i2c_slave_testunit_slave_cb); 15462306a36Sopenharmony_ci}; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_cistatic void i2c_slave_testunit_remove(struct i2c_client *client) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci struct testunit_data *tu = i2c_get_clientdata(client); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci cancel_delayed_work_sync(&tu->worker); 16162306a36Sopenharmony_ci i2c_slave_unregister(client); 16262306a36Sopenharmony_ci} 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_cistatic const struct i2c_device_id i2c_slave_testunit_id[] = { 16562306a36Sopenharmony_ci { "slave-testunit", 0 }, 16662306a36Sopenharmony_ci { } 16762306a36Sopenharmony_ci}; 16862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, i2c_slave_testunit_id); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic struct i2c_driver i2c_slave_testunit_driver = { 17162306a36Sopenharmony_ci .driver = { 17262306a36Sopenharmony_ci .name = "i2c-slave-testunit", 17362306a36Sopenharmony_ci }, 17462306a36Sopenharmony_ci .probe = i2c_slave_testunit_probe, 17562306a36Sopenharmony_ci .remove = i2c_slave_testunit_remove, 17662306a36Sopenharmony_ci .id_table = i2c_slave_testunit_id, 17762306a36Sopenharmony_ci}; 17862306a36Sopenharmony_cimodule_i2c_driver(i2c_slave_testunit_driver); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ciMODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>"); 18162306a36Sopenharmony_ciMODULE_DESCRIPTION("I2C slave mode test unit"); 18262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 183