| From e9f4254fd17b010cf78b57e3f5a6fe2202edbd4d Mon Sep 17 00:00:00 2001 |
| From: David Wang <davidwang@quantatw.com> |
| Date: Wed, 8 Nov 2023 09:37:12 +0800 |
| Subject: [PATCH] drivers: i2c: add slave-mqueue |
| |
| --- |
| drivers/i2c/Kconfig | 25 ++++ |
| drivers/i2c/Makefile | 1 + |
| drivers/i2c/i2c-slave-mqueue.c | 215 +++++++++++++++++++++++++++++++++ |
| 3 files changed, 241 insertions(+) |
| create mode 100644 drivers/i2c/i2c-slave-mqueue.c |
| |
| diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig |
| index 438905e2a1d0..5ffe13438b6f 100644 |
| --- a/drivers/i2c/Kconfig |
| +++ b/drivers/i2c/Kconfig |
| @@ -133,6 +133,31 @@ config I2C_SLAVE_TESTUNIT |
| multi-master, SMBus Host Notify, etc. Please read |
| Documentation/i2c/slave-testunit-backend.rst for further details. |
| |
| +config I2C_SLAVE_MQUEUE |
| + tristate "I2C mqueue (message queue) slave driver" |
| + help |
| + Some protocols over I2C are designed for bi-directional transferring |
| + messages by using I2C Master Write protocol. This driver is used to |
| + receive and queue messages from the remote I2C device. |
| + |
| + Userspace can get the messages by reading sysfs file that this driver |
| + exposes. |
| + |
| + This support is also available as a module. If so, the module will be |
| + called i2c-slave-mqueue. |
| + |
| +config I2C_SLAVE_MQUEUE_MESSAGE_SIZE |
| + int "The message size of I2C mqueue slave" |
| + depends on I2C_SLAVE_MQUEUE |
| + default 120 |
| + |
| +config I2C_SLAVE_MQUEUE_QUEUE_SIZE |
| + int "The queue size of I2C mqueue slave" |
| + depends on I2C_SLAVE_MQUEUE |
| + default 32 |
| + help |
| + This number MUST be power of 2. |
| + |
| endif |
| |
| config I2C_DEBUG_CORE |
| diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile |
| index c1d493dc9bac..0442e5cf8587 100644 |
| --- a/drivers/i2c/Makefile |
| +++ b/drivers/i2c/Makefile |
| @@ -17,5 +17,6 @@ obj-y += algos/ busses/ muxes/ |
| obj-$(CONFIG_I2C_STUB) += i2c-stub.o |
| obj-$(CONFIG_I2C_SLAVE_EEPROM) += i2c-slave-eeprom.o |
| obj-$(CONFIG_I2C_SLAVE_TESTUNIT) += i2c-slave-testunit.o |
| +obj-$(CONFIG_I2C_SLAVE_MQUEUE) += i2c-slave-mqueue.o |
| |
| ccflags-$(CONFIG_I2C_DEBUG_CORE) := -DDEBUG |
| diff --git a/drivers/i2c/i2c-slave-mqueue.c b/drivers/i2c/i2c-slave-mqueue.c |
| new file mode 100644 |
| index 000000000000..c17c4911928f |
| --- /dev/null |
| +++ b/drivers/i2c/i2c-slave-mqueue.c |
| @@ -0,0 +1,215 @@ |
| +// SPDX-License-Identifier: GPL-2.0 |
| +// Copyright (c) 2017 - 2018, Intel Corporation. |
| + |
| +#include <linux/i2c.h> |
| +#include <linux/kernel.h> |
| +#include <linux/module.h> |
| +#include <linux/device.h> |
| +#include <linux/of.h> |
| +#include <linux/slab.h> |
| +#include <linux/spinlock.h> |
| +#include <linux/sysfs.h> |
| + |
| +#define MQ_MSGBUF_SIZE CONFIG_I2C_SLAVE_MQUEUE_MESSAGE_SIZE |
| +#define MQ_QUEUE_SIZE CONFIG_I2C_SLAVE_MQUEUE_QUEUE_SIZE |
| +#define MQ_QUEUE_NEXT(x) (((x) + 1) & (MQ_QUEUE_SIZE - 1)) |
| + |
| +struct mq_msg { |
| + int len; |
| + u8 *buf; |
| +}; |
| + |
| +struct mq_queue { |
| + struct bin_attribute bin; |
| + struct kernfs_node *kn; |
| + |
| + spinlock_t lock; /* spinlock for queue index handling */ |
| + int in; |
| + int out; |
| + |
| + struct mq_msg *curr; |
| + int truncated; /* drop current if truncated */ |
| + struct mq_msg queue[MQ_QUEUE_SIZE]; |
| +}; |
| + |
| +static int i2c_slave_mqueue_callback(struct i2c_client *client, |
| + enum i2c_slave_event event, u8 *val) |
| +{ |
| + struct mq_queue *mq = i2c_get_clientdata(client); |
| + struct mq_msg *msg = mq->curr; |
| + int ret = 0; |
| + |
| + switch (event) { |
| + case I2C_SLAVE_WRITE_REQUESTED: |
| + mq->truncated = 0; |
| + |
| + msg->len = 1; |
| + msg->buf[0] = client->addr << 1; |
| + break; |
| + |
| + case I2C_SLAVE_WRITE_RECEIVED: |
| + if (msg->len < MQ_MSGBUF_SIZE) { |
| + msg->buf[msg->len++] = *val; |
| + } else { |
| + dev_err(&client->dev, "message is truncated!\n"); |
| + mq->truncated = 1; |
| + ret = -EINVAL; |
| + } |
| + break; |
| + |
| + case I2C_SLAVE_STOP: |
| + if (unlikely(mq->truncated || msg->len < 2)) |
| + break; |
| + |
| + spin_lock(&mq->lock); |
| + mq->in = MQ_QUEUE_NEXT(mq->in); |
| + mq->curr = &mq->queue[mq->in]; |
| + mq->curr->len = 0; |
| + |
| + /* Flush the oldest message */ |
| + if (mq->out == mq->in) |
| + mq->out = MQ_QUEUE_NEXT(mq->out); |
| + spin_unlock(&mq->lock); |
| + |
| + kernfs_notify(mq->kn); |
| + break; |
| + |
| + default: |
| + *val = 0xFF; |
| + break; |
| + } |
| + |
| + return ret; |
| +} |
| + |
| +static ssize_t i2c_slave_mqueue_bin_read(struct file *filp, |
| + struct kobject *kobj, |
| + struct bin_attribute *attr, |
| + char *buf, loff_t pos, size_t count) |
| +{ |
| + struct mq_queue *mq; |
| + struct mq_msg *msg; |
| + unsigned long flags; |
| + bool more = false; |
| + ssize_t ret = 0; |
| + |
| + mq = dev_get_drvdata(kobj_to_dev(kobj)); |
| + |
| + spin_lock_irqsave(&mq->lock, flags); |
| + if (mq->out != mq->in) { |
| + msg = &mq->queue[mq->out]; |
| + |
| + if (msg->len <= count) { |
| + ret = msg->len; |
| + memcpy(buf, msg->buf, ret); |
| + } else { |
| + ret = -EOVERFLOW; /* Drop this HUGE one. */ |
| + } |
| + |
| + mq->out = MQ_QUEUE_NEXT(mq->out); |
| + if (mq->out != mq->in) |
| + more = true; |
| + } |
| + spin_unlock_irqrestore(&mq->lock, flags); |
| + |
| + if (more) |
| + kernfs_notify(mq->kn); |
| + |
| + return ret; |
| +} |
| + |
| +static int i2c_slave_mqueue_probe(struct i2c_client *client, |
| + const struct i2c_device_id *id) |
| +{ |
| + struct device *dev = &client->dev; |
| + struct mq_queue *mq; |
| + int ret, i; |
| + void *buf; |
| + |
| + BUILD_BUG_ON(!is_power_of_2(MQ_QUEUE_SIZE)); |
| + |
| + mq = devm_kzalloc(dev, sizeof(*mq), GFP_KERNEL); |
| + if (!mq) |
| + return -ENOMEM; |
| + |
| + buf = devm_kmalloc_array(dev, MQ_QUEUE_SIZE, MQ_MSGBUF_SIZE, |
| + GFP_KERNEL); |
| + if (!buf) |
| + return -ENOMEM; |
| + |
| + for (i = 0; i < MQ_QUEUE_SIZE; i++) |
| + mq->queue[i].buf = buf + i * MQ_MSGBUF_SIZE; |
| + |
| + i2c_set_clientdata(client, mq); |
| + |
| + spin_lock_init(&mq->lock); |
| + mq->curr = &mq->queue[0]; |
| + |
| + sysfs_bin_attr_init(&mq->bin); |
| + mq->bin.attr.name = "slave-mqueue"; |
| + mq->bin.attr.mode = 0400; |
| + mq->bin.read = i2c_slave_mqueue_bin_read; |
| + mq->bin.size = MQ_MSGBUF_SIZE * MQ_QUEUE_SIZE; |
| + |
| + ret = sysfs_create_bin_file(&dev->kobj, &mq->bin); |
| + if (ret) |
| + return ret; |
| + |
| + mq->kn = kernfs_find_and_get(dev->kobj.sd, mq->bin.attr.name); |
| + if (!mq->kn) { |
| + sysfs_remove_bin_file(&dev->kobj, &mq->bin); |
| + return -EFAULT; |
| + } |
| + |
| + ret = i2c_slave_register(client, i2c_slave_mqueue_callback); |
| + if (ret) { |
| + kernfs_put(mq->kn); |
| + sysfs_remove_bin_file(&dev->kobj, &mq->bin); |
| + return ret; |
| + } |
| + |
| + return 0; |
| +} |
| + |
| +static int i2c_slave_mqueue_remove(struct i2c_client *client) |
| +{ |
| + struct mq_queue *mq = i2c_get_clientdata(client); |
| + |
| + i2c_slave_unregister(client); |
| + |
| + kernfs_put(mq->kn); |
| + sysfs_remove_bin_file(&client->dev.kobj, &mq->bin); |
| + |
| + return 0; |
| +} |
| + |
| +static const struct i2c_device_id i2c_slave_mqueue_id[] = { |
| + { "slave-mqueue", 0 }, |
| + { /* sentinel */ } |
| +}; |
| +MODULE_DEVICE_TABLE(i2c, i2c_slave_mqueue_id); |
| + |
| +#ifdef CONFIG_OF |
| +static const struct of_device_id i2c_slave_mqueue_of_match[] = { |
| + { |
| + .compatible = "i2c-slave-mqueue", |
| + }, |
| + { /* sentinel */ } |
| +}; |
| +MODULE_DEVICE_TABLE(of, i2c_slave_mqueue_of_match); |
| +#endif |
| + |
| +static struct i2c_driver i2c_slave_mqueue_driver = { |
| + .driver = { |
| + .name = "i2c-slave-mqueue", |
| + .of_match_table = of_match_ptr(i2c_slave_mqueue_of_match), |
| + }, |
| + .probe = i2c_slave_mqueue_probe, |
| + .remove = i2c_slave_mqueue_remove, |
| + .id_table = i2c_slave_mqueue_id, |
| +}; |
| +module_i2c_driver(i2c_slave_mqueue_driver); |
| + |
| +MODULE_LICENSE("GPL v2"); |
| +MODULE_AUTHOR("Haiyue Wang <haiyue.wang@linux.intel.com>"); |
| +MODULE_DESCRIPTION("I2C slave mode for receiving and queuing messages"); |
| -- |
| 2.25.1 |
| |