brintos

brintos / linux-shallow public Read only

0
0
Text · 6.6 KiB · 9fe3150 Raw
273 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * I2C slave mode testunit4 *5 * Copyright (C) 2020 by Wolfram Sang, Sang Engineering <wsa@sang-engineering.com>6 * Copyright (C) 2020 by Renesas Electronics Corporation7 */8 9#include <generated/utsrelease.h>10#include <linux/bitops.h>11#include <linux/completion.h>12#include <linux/gpio/consumer.h>13#include <linux/i2c.h>14#include <linux/init.h>15#include <linux/module.h>16#include <linux/of.h>17#include <linux/slab.h>18#include <linux/workqueue.h> /* FIXME: is system_long_wq the best choice? */19 20#define TU_VERSION_MAX_LENGTH 12821 22enum testunit_cmds {23	TU_CMD_READ_BYTES = 1,	/* save 0 for ABORT, RESET or similar */24	TU_CMD_SMBUS_HOST_NOTIFY,25	TU_CMD_SMBUS_BLOCK_PROC_CALL,26	TU_CMD_GET_VERSION_WITH_REP_START,27	TU_CMD_SMBUS_ALERT_REQUEST,28	TU_NUM_CMDS29};30 31enum testunit_regs {32	TU_REG_CMD,33	TU_REG_DATAL,34	TU_REG_DATAH,35	TU_REG_DELAY,36	TU_NUM_REGS37};38 39enum testunit_flags {40	TU_FLAG_IN_PROCESS,41};42 43struct testunit_data {44	unsigned long flags;45	u8 regs[TU_NUM_REGS];46	u8 reg_idx;47	u8 read_idx;48	struct i2c_client *client;49	struct delayed_work worker;50	struct gpio_desc *gpio;51	struct completion alert_done;52};53 54static char tu_version_info[] = "v" UTS_RELEASE "\n\0";55 56static int i2c_slave_testunit_smbalert_cb(struct i2c_client *client,57					  enum i2c_slave_event event, u8 *val)58{59	struct testunit_data *tu = i2c_get_clientdata(client);60 61	switch (event) {62	case I2C_SLAVE_READ_PROCESSED:63		gpiod_set_value(tu->gpio, 0);64		fallthrough;65	case I2C_SLAVE_READ_REQUESTED:66		*val = tu->regs[TU_REG_DATAL];67		break;68 69	case I2C_SLAVE_STOP:70		complete(&tu->alert_done);71		break;72 73	case I2C_SLAVE_WRITE_REQUESTED:74	case I2C_SLAVE_WRITE_RECEIVED:75		return -EOPNOTSUPP;76	}77 78	return 0;79}80 81static int i2c_slave_testunit_slave_cb(struct i2c_client *client,82				     enum i2c_slave_event event, u8 *val)83{84	struct testunit_data *tu = i2c_get_clientdata(client);85	bool is_proc_call = tu->reg_idx == 3 && tu->regs[TU_REG_DATAL] == 1 &&86			    tu->regs[TU_REG_CMD] == TU_CMD_SMBUS_BLOCK_PROC_CALL;87	bool is_get_version = tu->reg_idx == 3 &&88			      tu->regs[TU_REG_CMD] == TU_CMD_GET_VERSION_WITH_REP_START;89	int ret = 0;90 91	switch (event) {92	case I2C_SLAVE_WRITE_REQUESTED:93		if (test_bit(TU_FLAG_IN_PROCESS, &tu->flags))94			return -EBUSY;95 96		memset(tu->regs, 0, TU_NUM_REGS);97		tu->reg_idx = 0;98		tu->read_idx = 0;99		break;100 101	case I2C_SLAVE_WRITE_RECEIVED:102		if (test_bit(TU_FLAG_IN_PROCESS, &tu->flags))103			return -EBUSY;104 105		if (tu->reg_idx < TU_NUM_REGS)106			tu->regs[tu->reg_idx] = *val;107		else108			ret = -EMSGSIZE;109 110		if (tu->reg_idx <= TU_NUM_REGS)111			tu->reg_idx++;112 113		/* TU_REG_CMD always written at this point */114		if (tu->regs[TU_REG_CMD] >= TU_NUM_CMDS)115			ret = -EINVAL;116 117		break;118 119	case I2C_SLAVE_STOP:120		if (tu->reg_idx == TU_NUM_REGS) {121			set_bit(TU_FLAG_IN_PROCESS, &tu->flags);122			queue_delayed_work(system_long_wq, &tu->worker,123					   msecs_to_jiffies(10 * tu->regs[TU_REG_DELAY]));124		}125 126		/*127		 * Reset reg_idx to avoid that work gets queued again in case of128		 * STOP after a following read message. But do not clear TU regs129		 * here because we still need them in the workqueue!130		 */131		tu->reg_idx = 0;132		break;133 134	case I2C_SLAVE_READ_PROCESSED:135		/* Advance until we reach the NUL character */136		if (is_get_version && tu_version_info[tu->read_idx] != 0)137			tu->read_idx++;138		else if (is_proc_call && tu->regs[TU_REG_DATAH])139			tu->regs[TU_REG_DATAH]--;140 141		fallthrough;142 143	case I2C_SLAVE_READ_REQUESTED:144		if (is_get_version)145			*val = tu_version_info[tu->read_idx];146		else if (is_proc_call)147			*val = tu->regs[TU_REG_DATAH];148		else149			*val = test_bit(TU_FLAG_IN_PROCESS, &tu->flags) ?150					tu->regs[TU_REG_CMD] : 0;151		break;152	}153 154	return ret;155}156 157static void i2c_slave_testunit_work(struct work_struct *work)158{159	struct testunit_data *tu = container_of(work, struct testunit_data, worker.work);160	unsigned long time_left;161	struct i2c_msg msg;162	u8 msgbuf[256];163	u16 orig_addr;164	int ret = 0;165 166	msg.addr = I2C_CLIENT_END;167	msg.buf = msgbuf;168 169	switch (tu->regs[TU_REG_CMD]) {170	case TU_CMD_READ_BYTES:171		msg.addr = tu->regs[TU_REG_DATAL];172		msg.flags = I2C_M_RD;173		msg.len = tu->regs[TU_REG_DATAH];174		break;175 176	case TU_CMD_SMBUS_HOST_NOTIFY:177		msg.addr = 0x08;178		msg.flags = 0;179		msg.len = 3;180		msgbuf[0] = tu->client->addr;181		msgbuf[1] = tu->regs[TU_REG_DATAL];182		msgbuf[2] = tu->regs[TU_REG_DATAH];183		break;184 185	case TU_CMD_SMBUS_ALERT_REQUEST:186		i2c_slave_unregister(tu->client);187		orig_addr = tu->client->addr;188		tu->client->addr = 0x0c;189		ret = i2c_slave_register(tu->client, i2c_slave_testunit_smbalert_cb);190		if (ret)191			goto out_smbalert;192 193		reinit_completion(&tu->alert_done);194		gpiod_set_value(tu->gpio, 1);195		time_left = wait_for_completion_timeout(&tu->alert_done, HZ);196		if (!time_left)197			ret = -ETIMEDOUT;198 199		i2c_slave_unregister(tu->client);200out_smbalert:201		tu->client->addr = orig_addr;202		i2c_slave_register(tu->client, i2c_slave_testunit_slave_cb);203		break;204 205	default:206		break;207	}208 209	if (msg.addr != I2C_CLIENT_END) {210		ret = i2c_transfer(tu->client->adapter, &msg, 1);211		/* convert '0 msgs transferred' to errno */212		ret = (ret == 0) ? -EIO : ret;213	}214 215	if (ret < 0)216		dev_err(&tu->client->dev, "CMD%02X failed (%d)\n", tu->regs[TU_REG_CMD], ret);217 218	clear_bit(TU_FLAG_IN_PROCESS, &tu->flags);219}220 221static int i2c_slave_testunit_probe(struct i2c_client *client)222{223	struct testunit_data *tu;224 225	tu = devm_kzalloc(&client->dev, sizeof(struct testunit_data), GFP_KERNEL);226	if (!tu)227		return -ENOMEM;228 229	tu->client = client;230	i2c_set_clientdata(client, tu);231	init_completion(&tu->alert_done);232	INIT_DELAYED_WORK(&tu->worker, i2c_slave_testunit_work);233 234	tu->gpio = devm_gpiod_get_index_optional(&client->dev, NULL, 0, GPIOD_OUT_LOW);235	if (gpiod_cansleep(tu->gpio)) {236		dev_err(&client->dev, "GPIO access which may sleep is not allowed\n");237		return -EDEADLK;238	}239 240	if (sizeof(tu_version_info) > TU_VERSION_MAX_LENGTH)241		tu_version_info[TU_VERSION_MAX_LENGTH - 1] = 0;242 243	return i2c_slave_register(client, i2c_slave_testunit_slave_cb);244};245 246static void i2c_slave_testunit_remove(struct i2c_client *client)247{248	struct testunit_data *tu = i2c_get_clientdata(client);249 250	cancel_delayed_work_sync(&tu->worker);251	i2c_slave_unregister(client);252}253 254static const struct i2c_device_id i2c_slave_testunit_id[] = {255	{ "slave-testunit" },256	{ }257};258MODULE_DEVICE_TABLE(i2c, i2c_slave_testunit_id);259 260static struct i2c_driver i2c_slave_testunit_driver = {261	.driver = {262		.name = "i2c-slave-testunit",263	},264	.probe = i2c_slave_testunit_probe,265	.remove = i2c_slave_testunit_remove,266	.id_table = i2c_slave_testunit_id,267};268module_i2c_driver(i2c_slave_testunit_driver);269 270MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>");271MODULE_DESCRIPTION("I2C slave mode test unit");272MODULE_LICENSE("GPL v2");273