summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMeghana Barkalle <mbarkalle@google.com>2023-03-24 00:42:49 +0000
committerTreehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com>2023-12-13 16:36:31 +0000
commitf9d71a39111d947e0583e9c40d0e5f713f5a2a19 (patch)
tree934586356d9eb5bef183019d8bd95fbea06eb472
parentccfb61d41a2961cbacbb2be8b1488447433f04e0 (diff)
downloadlwis-f9d71a39111d947e0583e9c40d0e5f713f5a2a19.tar.gz
LWIS: Implement I2C Bus Manager
Implementation of I2C Bus Manager. This will provide a means to coordinate I2C transfers on a given bus connected to multiple camera I2C peripherals. Test: Basic camera switch, preview, video, liveshot and snapshot Bug: 299130975 Change-Id: Ic1a85a3f1ad913135ac7e01704e1b7831355adde Signed-off-by: Meghana Barkalle <mbarkalle@google.com> (cherry picked from commit ad6945dd2b326f1c9e8e5bb0b7a47b3fa9cd19cc)
-rw-r--r--Kbuild2
-rw-r--r--lwis_device.c14
-rw-r--r--lwis_device.h2
-rw-r--r--lwis_device_i2c.c22
-rw-r--r--lwis_device_i2c.h3
-rw-r--r--lwis_i2c_bus_manager.c524
-rw-r--r--lwis_i2c_bus_manager.h106
-rw-r--r--lwis_i2c_sched.c119
-rw-r--r--lwis_i2c_sched.h37
-rw-r--r--lwis_ioctl.c3
-rw-r--r--lwis_periodic_io.c104
-rw-r--r--lwis_transaction.c66
-rw-r--r--lwis_util.c5
-rw-r--r--lwis_util.h5
14 files changed, 965 insertions, 47 deletions
diff --git a/Kbuild b/Kbuild
index 7dcb1d9..37d3f3a 100644
--- a/Kbuild
+++ b/Kbuild
@@ -24,6 +24,8 @@ lwis-objs += lwis_io_entry.o
lwis-objs += lwis_allocator.o
lwis-objs += lwis_version.o
lwis-objs += lwis_fence.o
+lwis-objs += lwis_i2c_bus_manager.o
+lwis-objs += lwis_i2c_sched.o
# Anchorage specific files
ifeq ($(CONFIG_SOC_GS101), y)
diff --git a/lwis_device.c b/lwis_device.c
index 1ff65b2..60b5dc4 100644
--- a/lwis_device.c
+++ b/lwis_device.c
@@ -41,6 +41,7 @@
#include "lwis_util.h"
#include "lwis_version.h"
#include "lwis_trace.h"
+#include "lwis_i2c_bus_manager.h"
#ifdef CONFIG_OF
#include "lwis_dt.h"
@@ -84,6 +85,13 @@ static void transaction_work_func(struct kthread_work *work)
lwis_process_worker_queue(client);
}
+static void i2c_process_work_func(struct kthread_work *work)
+{
+ /* i2c_work stores the context of the lwis_client submitting the transfer request */
+ struct lwis_client *client = container_of(work, struct lwis_client, i2c_work);
+ lwis_i2c_bus_manager_process_worker_queue(client);
+}
+
/*
* lwis_open: Opening an instance of a LWIS device
*/
@@ -135,6 +143,7 @@ static int lwis_open(struct inode *node, struct file *fp)
lwis_allocator_init(lwis_dev);
kthread_init_work(&lwis_client->transaction_work, transaction_work_func);
+ kthread_init_work(&lwis_client->i2c_work, i2c_process_work_func);
/* Start transaction processor task */
lwis_transaction_init(lwis_client);
@@ -226,6 +235,7 @@ static int lwis_release_client(struct lwis_client *lwis_client)
spin_unlock_irqrestore(&lwis_dev->lock, flags);
kfree(lwis_client);
+
return 0;
}
@@ -1588,6 +1598,10 @@ void lwis_base_unprobe(struct lwis_device *unprobe_lwis_dev)
&lwis_dev->plat_dev->dev);
lwis_dev->irq_gpios_info.gpios = NULL;
}
+
+ /* Disconnect from the bus manager */
+ lwis_i2c_bus_manager_disconnect(lwis_dev);
+
/* Destroy device */
if (!IS_ERR_OR_NULL(lwis_dev->dev)) {
device_destroy(core.dev_class,
diff --git a/lwis_device.h b/lwis_device.h
index c011775..75881fa 100644
--- a/lwis_device.h
+++ b/lwis_device.h
@@ -345,6 +345,8 @@ struct lwis_client {
struct list_head node;
/* Mark if the client called device enable */
bool is_enabled;
+ /* Work item to schedule I2C transfers */
+ struct kthread_work i2c_work;
};
/*
diff --git a/lwis_device_i2c.c b/lwis_device_i2c.c
index ae74232..ad513b8 100644
--- a/lwis_device_i2c.c
+++ b/lwis_device_i2c.c
@@ -274,26 +274,14 @@ static int lwis_i2c_device_probe(struct platform_device *plat_dev)
goto error_probe;
}
- /* Create associated kworker threads */
- ret = lwis_create_kthread_workers(&i2c_dev->base_dev);
+ /* Create I2C Bus Manager */
+ ret = lwis_i2c_bus_manager_create(i2c_dev);
if (ret) {
- dev_err(i2c_dev->base_dev.dev, "Failed to create lwis_i2c_kthread");
+ dev_err(i2c_dev->base_dev.dev, "Error in i2c bus manager creation\n");
lwis_base_unprobe(&i2c_dev->base_dev);
goto error_probe;
}
- if (i2c_dev->base_dev.transaction_thread_priority != 0) {
- ret = lwis_set_kthread_priority(&i2c_dev->base_dev,
- i2c_dev->base_dev.transaction_worker_thread,
- i2c_dev->base_dev.transaction_thread_priority);
- if (ret) {
- dev_err(i2c_dev->base_dev.dev,
- "Failed to set LWIS I2C transaction kthread priority (%d)", ret);
- lwis_base_unprobe(&i2c_dev->base_dev);
- goto error_probe;
- }
- }
-
dev_info(i2c_dev->base_dev.dev, "I2C Device Probe: Success\n");
return 0;
@@ -375,6 +363,8 @@ int __init lwis_i2c_device_init(void)
pr_info("I2C device initialization\n");
+ lwis_i2c_bus_manager_list_initialize();
+
ret = platform_driver_register(&lwis_driver);
if (ret) {
pr_err("platform_driver_register failed: %d\n", ret);
@@ -389,6 +379,8 @@ int __init lwis_i2c_device_init(void)
int lwis_i2c_device_deinit(void)
{
+ lwis_i2c_bus_manager_list_deinitialize();
+
platform_driver_unregister(&lwis_driver);
return 0;
}
diff --git a/lwis_device_i2c.h b/lwis_device_i2c.h
index 7d52d1f..03c393f 100644
--- a/lwis_device_i2c.h
+++ b/lwis_device_i2c.h
@@ -15,6 +15,7 @@
#include <linux/pinctrl/consumer.h>
#include "lwis_device.h"
+#include "lwis_i2c_bus_manager.h"
#define MAX_I2C_LOCK_NUM 8
@@ -33,6 +34,8 @@ struct lwis_i2c_device {
u32 i2c_lock_group_id;
/* Mutex shared by the same group id's I2C devices */
struct mutex *group_i2c_lock;
+ /* Pointer to the I2C bus manager for this device */
+ struct lwis_i2c_bus_manager *i2c_bus_manager;
};
int lwis_i2c_device_deinit(void);
diff --git a/lwis_i2c_bus_manager.c b/lwis_i2c_bus_manager.c
new file mode 100644
index 0000000..1b3525e
--- /dev/null
+++ b/lwis_i2c_bus_manager.c
@@ -0,0 +1,524 @@
+/*
+ * Google LWIS I2C BUS Manager
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME "-i2c-bus-manager: " fmt
+
+#include "lwis_device.h"
+#include "lwis_i2c_bus_manager.h"
+#include "lwis_device_i2c.h"
+#include "lwis_i2c_sched.h"
+
+/* Defines the global list of bus managers shared among various I2C devices
+ * Each manager would control the transfers on a single I2C bus */
+static struct mutex i2c_bus_manager_list_lock;
+static struct lwis_i2c_bus_manager_list i2c_bus_manager_list;
+
+/*
+ * insert_bus_manager_id_in_list:
+ * Inserts the newly created instance of I2C bus manager in the list
+*/
+static int insert_bus_manager_id_in_list(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ int i2c_bus_handle)
+{
+ struct lwis_i2c_bus_manager_identifier *i2c_bus_manager_identifier_node = NULL;
+
+ if (!i2c_bus_manager)
+ return -EINVAL;
+
+ i2c_bus_manager_identifier_node =
+ kzalloc(sizeof(struct lwis_i2c_bus_manager_identifier), GFP_KERNEL);
+ if (!i2c_bus_manager_identifier_node) {
+ pr_err("Failed to allocate lwis i2c bus manager id list node\n");
+ return -ENOMEM;
+ }
+
+ i2c_bus_manager_identifier_node->i2c_bus_manager_handle = i2c_bus_handle;
+ i2c_bus_manager_identifier_node->i2c_bus_manager = i2c_bus_manager;
+ INIT_LIST_HEAD(&i2c_bus_manager_identifier_node->i2c_bus_manager_list_node);
+
+ mutex_lock(&i2c_bus_manager_list_lock);
+ list_add_tail(&i2c_bus_manager_identifier_node->i2c_bus_manager_list_node,
+ &i2c_bus_manager_list.i2c_bus_manager_list_head);
+ mutex_unlock(&i2c_bus_manager_list_lock);
+
+ return 0;
+}
+
+/*
+ * delete_bus_manager_id_in_list:
+ * Deletes the newly created instance of I2C bus manager in the list
+*/
+static void delete_bus_manager_id_in_list(int i2c_bus_handle)
+{
+ struct lwis_i2c_bus_manager_identifier *i2c_bus_manager_identifier_node = NULL;
+ struct list_head *i2c_bus_manager_list_node = NULL;
+ struct list_head *i2c_bus_manager_list_tmp_node = NULL;
+
+ mutex_lock(&i2c_bus_manager_list_lock);
+ list_for_each_safe (i2c_bus_manager_list_node, i2c_bus_manager_list_tmp_node,
+ &i2c_bus_manager_list.i2c_bus_manager_list_head) {
+ i2c_bus_manager_identifier_node = list_entry(i2c_bus_manager_list_node,
+ struct lwis_i2c_bus_manager_identifier,
+ i2c_bus_manager_list_node);
+ if (i2c_bus_manager_identifier_node->i2c_bus_manager_handle == i2c_bus_handle) {
+ list_del(&i2c_bus_manager_identifier_node->i2c_bus_manager_list_node);
+ kfree(i2c_bus_manager_identifier_node);
+ i2c_bus_manager_identifier_node = NULL;
+ break;
+ }
+ }
+ mutex_unlock(&i2c_bus_manager_list_lock);
+}
+
+/*
+ * find_i2c_bus_manager:
+ * Returns a valid I2C Bus Manager for a valid i2c_bus_handle.
+ * Returns NULL if the bus manager hasn't been created for this handle.
+*/
+static struct lwis_i2c_bus_manager *find_i2c_bus_manager(int i2c_bus_handle)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ struct list_head *i2c_bus_manager_list_node = NULL;
+ struct list_head *i2c_bus_manager_list_tmp_node = NULL;
+ struct lwis_i2c_bus_manager_identifier *i2c_bus_manager_identifier = NULL;
+
+ mutex_lock(&i2c_bus_manager_list_lock);
+ list_for_each_safe (i2c_bus_manager_list_node, i2c_bus_manager_list_tmp_node,
+ &i2c_bus_manager_list.i2c_bus_manager_list_head) {
+ i2c_bus_manager_identifier = list_entry(i2c_bus_manager_list_node,
+ struct lwis_i2c_bus_manager_identifier,
+ i2c_bus_manager_list_node);
+ if (i2c_bus_manager_identifier->i2c_bus_manager_handle == i2c_bus_handle) {
+ i2c_bus_manager = i2c_bus_manager_identifier->i2c_bus_manager;
+ break;
+ }
+ }
+ mutex_unlock(&i2c_bus_manager_list_lock);
+
+ return i2c_bus_manager;
+}
+
+/*
+ * create_i2c_kthread_workers:
+ * Creates I2C worker threads, one per bus
+*/
+static int create_i2c_kthread_workers(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ char i2c_bus_thread_name[LWIS_MAX_NAME_STRING_LEN];
+ if (!i2c_bus_manager) {
+ dev_err(lwis_dev->dev, "lwis_create_kthread_workers: I2C Bus Manager is NULL\n");
+ return -ENODEV;
+ }
+ scnprintf(i2c_bus_thread_name, LWIS_MAX_NAME_STRING_LEN, "lwis_%s",
+ i2c_bus_manager->i2c_bus_name);
+ kthread_init_worker(&i2c_bus_manager->i2c_bus_worker);
+ i2c_bus_manager->i2c_bus_worker_thread = kthread_run(
+ kthread_worker_fn, &i2c_bus_manager->i2c_bus_worker, i2c_bus_thread_name);
+ if (IS_ERR(i2c_bus_manager->i2c_bus_worker_thread)) {
+ dev_err(lwis_dev->dev, "Creation of i2c_bus_worker_thread failed for bus %s\n",
+ i2c_bus_manager->i2c_bus_name);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * check_i2c_thread_priority:
+ * Checks if the lwis device being connected has the same priority as other I2C threads
+ * Prints a warning message if there is a difference between the priorities
+*/
+static void check_i2c_thread_priority(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ if (i2c_bus_manager->i2c_bus_thread_priority != lwis_dev->transaction_thread_priority) {
+ dev_warn(
+ lwis_dev->dev,
+ "I2C bus manager thread %s priority(%d) is not the same as device thread priority(%d)\n",
+ i2c_bus_manager->i2c_bus_name, i2c_bus_manager->i2c_bus_thread_priority,
+ lwis_dev->transaction_thread_priority);
+ }
+}
+
+/*
+ * set_i2c_thread_priority:
+ * Sets the priority for I2C threads
+*/
+static int set_i2c_thread_priority(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ int ret = 0;
+ i2c_bus_manager->i2c_bus_thread_priority = lwis_dev->transaction_thread_priority;
+ if (i2c_bus_manager->i2c_bus_thread_priority != 0) {
+ ret = lwis_set_kthread_priority(lwis_dev, i2c_bus_manager->i2c_bus_worker_thread,
+ i2c_bus_manager->i2c_bus_thread_priority);
+ }
+ return ret;
+}
+
+/*
+ * is_valid_connected_device:
+ * Makes sure a valid client connected to this I2C executes the job on this manager
+ */
+static bool is_valid_connected_device(struct lwis_device *lwis_dev,
+ struct lwis_i2c_bus_manager *i2c_bus_manager)
+{
+ struct lwis_i2c_connected_device *connected_i2c_device;
+ struct list_head *i2c_connected_device_node, *i2c_connected_device_tmp_node;
+
+ if ((lwis_dev == NULL) || (i2c_bus_manager == NULL)) {
+ return false;
+ }
+
+ list_for_each_safe (i2c_connected_device_node, i2c_connected_device_tmp_node,
+ &i2c_bus_manager->i2c_connected_devices) {
+ connected_i2c_device =
+ list_entry(i2c_connected_device_node, struct lwis_i2c_connected_device,
+ connected_device_node);
+ if (connected_i2c_device->connected_device == lwis_dev) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/*
+ * set_i2c_bus_manager_name:
+ * Builds and sets the I2C Bus manager name
+*/
+static void set_i2c_bus_manager_name(struct lwis_i2c_bus_manager *i2c_bus_manager)
+{
+ scnprintf(i2c_bus_manager->i2c_bus_name, LWIS_MAX_NAME_STRING_LEN, "I2C_Bus_%d",
+ i2c_bus_manager->i2c_bus_id);
+}
+
+/*
+ * destroy_i2c_bus_manager:
+ * Destroys this instance of the I2C bus manager
+ */
+static void destroy_i2c_bus_manager(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ unsigned long flags;
+ if (i2c_bus_manager) {
+ dev_info(lwis_dev->dev, "Destroying I2C Bus Manager: %s\n",
+ i2c_bus_manager->i2c_bus_name);
+ spin_lock_irqsave(&i2c_bus_manager->i2c_process_queue_lock, flags);
+ lwis_i2c_process_request_queue_destroy(&i2c_bus_manager->i2c_bus_process_queue);
+ spin_unlock_irqrestore(&i2c_bus_manager->i2c_process_queue_lock, flags);
+
+ /* Delete the bus manager instance from the list */
+ delete_bus_manager_id_in_list(i2c_bus_manager->i2c_bus_id);
+
+ /* Free the bus manager */
+ kfree(i2c_bus_manager);
+ i2c_bus_manager = NULL;
+ }
+}
+
+/*
+ * connect_i2c_bus_manager:
+ * Connects a lwis device to this instance of the I2C bus manager.
+*/
+static int connect_i2c_bus_manager(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device *lwis_dev)
+{
+ int ret = 0;
+ struct lwis_i2c_connected_device *connected_i2c_device;
+
+ if ((!lwis_dev) || (!i2c_bus_manager)) {
+ pr_err("Null lwis device or bus manager\n");
+ return -EINVAL;
+ }
+
+ if (!lwis_check_device_type(lwis_dev, DEVICE_TYPE_I2C)) {
+ dev_err(lwis_dev->dev,
+ "Failed trying to connect non I2C device to a I2C bus manager\n");
+ return -EINVAL;
+ }
+
+ connected_i2c_device = kzalloc(sizeof(struct lwis_i2c_connected_device), GFP_KERNEL);
+ if (!connected_i2c_device) {
+ dev_err(lwis_dev->dev, "Failed to connect device to I2C Bus Manager\n");
+ return -ENOMEM;
+ }
+ connected_i2c_device->connected_device = lwis_dev;
+ INIT_LIST_HEAD(&connected_i2c_device->connected_device_node);
+ list_add_tail(&connected_i2c_device->connected_device_node,
+ &i2c_bus_manager->i2c_connected_devices);
+ i2c_bus_manager->number_of_connected_devices++;
+
+ return ret;
+}
+
+/*
+ * lwis_i2c_bus_manager_process_worker_queue:
+ * Function to be called by i2c bus manager worker thread to
+ * pick the next I2C device that is scheduled for transfer
+ */
+void lwis_i2c_bus_manager_process_worker_queue(struct lwis_client *client)
+{
+ /* Get the correct I2C Bus manager to process it's queue */
+ struct lwis_device *lwis_dev = NULL;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+
+ /* The transfers will be processed in fifo order */
+ struct lwis_device **dequeuing_dev = NULL;
+ struct lwis_client *client_to_process = NULL;
+ struct lwis_device *lwis_dev_to_process = NULL;
+ unsigned long flags;
+
+ lwis_dev = client->lwis_dev;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
+ if (i2c_bus_manager) {
+ spin_lock_irqsave(&i2c_bus_manager->i2c_process_queue_lock, flags);
+ dequeuing_dev = lwis_i2c_process_request_queue_dequeue_request(
+ &i2c_bus_manager->i2c_bus_process_queue);
+ spin_unlock_irqrestore(&i2c_bus_manager->i2c_process_queue_lock, flags);
+
+ if (dequeuing_dev) {
+ lwis_dev_to_process = *dequeuing_dev;
+ if (is_valid_connected_device(lwis_dev_to_process, i2c_bus_manager)) {
+ client_to_process =
+ container_of(dequeuing_dev, struct lwis_client, lwis_dev);
+ lwis_process_transactions_in_queue(client_to_process);
+ lwis_process_periodic_io_in_queue(client_to_process);
+ }
+ }
+ }
+}
+
+/*
+ * lwis_i2c_bus_manager_create:
+ * Creates a new instance of I2C bus manager
+ */
+int lwis_i2c_bus_manager_create(struct lwis_i2c_device *i2c_dev)
+{
+ int ret = 0;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ struct lwis_device *i2c_base_device = &i2c_dev->base_dev;
+
+ if (!lwis_check_device_type(i2c_base_device, DEVICE_TYPE_I2C)) {
+ return 0;
+ }
+
+ i2c_bus_manager = find_i2c_bus_manager(i2c_dev->adapter->nr);
+ if (!i2c_bus_manager) {
+ /* Allocate memory for I2C Bus Manager */
+ i2c_bus_manager = kzalloc(sizeof(struct lwis_i2c_bus_manager), GFP_KERNEL);
+ if (!i2c_bus_manager) {
+ dev_err(i2c_base_device->dev, "Failed to allocate lwis i2c bus manager\n");
+ return -ENOMEM;
+ }
+
+ i2c_bus_manager->i2c_bus_id = i2c_dev->adapter->nr;
+ set_i2c_bus_manager_name(i2c_bus_manager);
+
+ /* Mutex and Lock initializations */
+ mutex_init(&i2c_bus_manager->i2c_bus_lock);
+ spin_lock_init(&i2c_bus_manager->i2c_process_queue_lock);
+
+ /* List initializations */
+ INIT_LIST_HEAD(&i2c_bus_manager->i2c_connected_devices);
+
+ /* Create a I2C transfer process queue */
+ lwis_i2c_process_request_queue_initialize(&i2c_bus_manager->i2c_bus_process_queue);
+
+ /* Insert this instance of bus manager in the bus manager list */
+ ret = insert_bus_manager_id_in_list(i2c_bus_manager, i2c_dev->adapter->nr);
+ if (ret < 0) {
+ goto error_creating_i2c_bus_manager;
+ }
+
+ /* Create worker thread to serve this bus manager */
+ ret = create_i2c_kthread_workers(i2c_bus_manager, i2c_base_device);
+ if (ret < 0) {
+ goto error_creating_i2c_bus_manager;
+ }
+
+ /* Set priority for the worker threads */
+ ret = set_i2c_thread_priority(i2c_bus_manager, i2c_base_device);
+ if (ret < 0) {
+ goto error_creating_i2c_bus_manager;
+ }
+ }
+
+ /* Check the current device's thread priority with respect to the bus priority */
+ check_i2c_thread_priority(i2c_bus_manager, i2c_base_device);
+
+ /* Connect this lwis device to the I2C Bus manager found/created */
+ ret = connect_i2c_bus_manager(i2c_bus_manager, i2c_base_device);
+ if (ret < 0) {
+ goto error_creating_i2c_bus_manager;
+ }
+
+ dev_info(i2c_base_device->dev,
+ "I2C Bus Manager: %s Connected Device: %s Connected device count: %d\n",
+ i2c_bus_manager->i2c_bus_name, i2c_base_device->name,
+ i2c_bus_manager->number_of_connected_devices);
+
+ i2c_dev->i2c_bus_manager = i2c_bus_manager;
+ return ret;
+
+error_creating_i2c_bus_manager:
+ dev_err(i2c_base_device->dev, "Error creating I2C Bus Manager\n");
+ if (i2c_bus_manager) {
+ kfree(i2c_bus_manager);
+ i2c_bus_manager = NULL;
+ }
+ return -EINVAL;
+}
+
+/*
+ * lwis_i2c_bus_manager_disconnect:
+ * Disconnects a lwis device from this instance of the I2C bus manager.
+ * Doesn't destroy the instance of I2C bus manager
+*/
+void lwis_i2c_bus_manager_disconnect(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager;
+ struct lwis_i2c_connected_device *connected_i2c_device;
+ struct list_head *i2c_connected_device_node, *i2c_connected_device_tmp_node;
+ struct lwis_i2c_device *i2c_dev = NULL;
+
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (!i2c_bus_manager) {
+ return;
+ }
+
+ list_for_each_safe (i2c_connected_device_node, i2c_connected_device_tmp_node,
+ &i2c_bus_manager->i2c_connected_devices) {
+ connected_i2c_device =
+ list_entry(i2c_connected_device_node, struct lwis_i2c_connected_device,
+ connected_device_node);
+ /* Reset the bus manager pointer for this i2c device */
+ i2c_dev = container_of(lwis_dev, struct lwis_i2c_device, base_dev);
+ i2c_dev->i2c_bus_manager = NULL;
+
+ if (connected_i2c_device->connected_device == lwis_dev) {
+ list_del(&connected_i2c_device->connected_device_node);
+ kfree(connected_i2c_device);
+ connected_i2c_device = NULL;
+ --i2c_bus_manager->number_of_connected_devices;
+
+ /* Destroy the bus manager instance if there
+ * are no more I2C devices connected to it
+ */
+ if (i2c_bus_manager->number_of_connected_devices == 0) {
+ destroy_i2c_bus_manager(i2c_bus_manager, lwis_dev);
+ }
+ return;
+ }
+ }
+}
+
+/* lwis_i2c_bus_manager_enqueue_transfer_request:
+ * Enqueues I2C transfer request from a requesting device on the I2C Scheduler
+ */
+int lwis_i2c_bus_manager_enqueue_transfer_request(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device **lwis_dev)
+{
+ int ret = 0;
+ struct lwis_device *enqueuing_dev = *lwis_dev;
+ unsigned long flags;
+
+ if (lwis_check_device_type(enqueuing_dev, DEVICE_TYPE_I2C)) {
+ spin_lock_irqsave(&i2c_bus_manager->i2c_process_queue_lock, flags);
+ ret = lwis_i2c_process_request_queue_enqueue_request(
+ &i2c_bus_manager->i2c_bus_process_queue, lwis_dev);
+ spin_unlock_irqrestore(&i2c_bus_manager->i2c_process_queue_lock, flags);
+ }
+ return ret;
+}
+
+/* lwis_i2c_bus_manager_lock_i2c_bus:
+ * Locks the I2C bus for a given I2C Lwis Device
+ */
+void lwis_i2c_bus_manager_lock_i2c_bus(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (i2c_bus_manager) {
+ mutex_lock(&i2c_bus_manager->i2c_bus_lock);
+ }
+}
+
+/* lwis_i2c_bus_manager_unlock_i2c_bus:
+ * Unlocks the I2C bus for a given I2C Lwis Device
+ */
+void lwis_i2c_bus_manager_unlock_i2c_bus(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (i2c_bus_manager) {
+ mutex_unlock(&i2c_bus_manager->i2c_bus_lock);
+ }
+}
+
+/* lwis_i2c_bus_managlwis_i2c_bus_manager_get_managerr_get:
+ * Gets I2C Bus Manager for a given lwis device
+ */
+struct lwis_i2c_bus_manager *lwis_i2c_bus_manager_get_manager(struct lwis_device *lwis_dev)
+{
+ struct lwis_i2c_device *i2c_dev = NULL;
+ if (lwis_check_device_type(lwis_dev, DEVICE_TYPE_I2C)) {
+ i2c_dev = container_of(lwis_dev, struct lwis_i2c_device, base_dev);
+ if (i2c_dev) {
+ return i2c_dev->i2c_bus_manager;
+ }
+ }
+ return NULL;
+}
+
+void lwis_i2c_bus_manager_flush_i2c_worker(struct lwis_device *lwis_dev)
+{
+ unsigned long process_queue_flags;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
+ if (i2c_bus_manager == NULL)
+ return;
+
+ kthread_flush_worker(&i2c_bus_manager->i2c_bus_worker);
+
+ /* After flushing the worker the process queue should be empty.
+ * This destroy is to make sure there are no more requests to be handled. */
+ spin_lock_irqsave(&i2c_bus_manager->i2c_process_queue_lock, process_queue_flags);
+ lwis_i2c_process_request_queue_destroy(&i2c_bus_manager->i2c_bus_process_queue);
+ spin_unlock_irqrestore(&i2c_bus_manager->i2c_process_queue_lock, process_queue_flags);
+}
+
+void lwis_i2c_bus_manager_list_initialize(void)
+{
+ /* initialize_i2c_bus_manager_list */
+ mutex_init(&i2c_bus_manager_list_lock);
+ INIT_LIST_HEAD(&i2c_bus_manager_list.i2c_bus_manager_list_head);
+}
+
+void lwis_i2c_bus_manager_list_deinitialize(void)
+{
+ struct list_head *i2c_bus_manager_list_node, *i2c_bus_manager_list_tmp_node;
+ struct lwis_i2c_bus_manager_identifier *i2c_bus_manager_identifier;
+
+ /* deinitialize_i2c_bus_manager_list */
+ mutex_lock(&i2c_bus_manager_list_lock);
+ list_for_each_safe (i2c_bus_manager_list_node, i2c_bus_manager_list_tmp_node,
+ &i2c_bus_manager_list.i2c_bus_manager_list_head) {
+ i2c_bus_manager_identifier = list_entry(i2c_bus_manager_list_node,
+ struct lwis_i2c_bus_manager_identifier,
+ i2c_bus_manager_list_node);
+ i2c_bus_manager_identifier->i2c_bus_manager = NULL;
+ list_del(&i2c_bus_manager_identifier->i2c_bus_manager_list_node);
+ kfree(i2c_bus_manager_identifier);
+ i2c_bus_manager_identifier = NULL;
+ }
+ mutex_unlock(&i2c_bus_manager_list_lock);
+} \ No newline at end of file
diff --git a/lwis_i2c_bus_manager.h b/lwis_i2c_bus_manager.h
new file mode 100644
index 0000000..ca40ae8
--- /dev/null
+++ b/lwis_i2c_bus_manager.h
@@ -0,0 +1,106 @@
+
+/*
+ * Google LWIS I2C Bus Manager
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef LWIS_I2C_BUS_MANAGER_H_
+#define LWIS_I2C_BUS_MANAGER_H_
+
+#include "lwis_device.h"
+#include "lwis_util.h"
+#include "lwis_periodic_io.h"
+#include "lwis_transaction.h"
+
+// Forward declaration
+struct lwis_i2c_device;
+
+/* struct lwis_i2c_bus_manager_list:
+ * Holds I2C bus manager list */
+struct lwis_i2c_bus_manager_list {
+ struct list_head i2c_bus_manager_list_head;
+};
+
+/* struct lwis_i2c_bus_manager_identifier:
+ * Holds a pointer to the I2C bus manager */
+struct lwis_i2c_bus_manager_identifier {
+ struct list_head i2c_bus_manager_list_node;
+ struct lwis_i2c_bus_manager *i2c_bus_manager;
+ int i2c_bus_manager_handle;
+};
+
+/* lwis_i2c_process_queue:
+ * This maintains the process queue for a given I2C bus.
+ * This is a collection of process request nodes that identify
+ * the lwis device requests in order they were queued.
+ * The scheduler is set to operate requests in a
+ * first in-first out manner, starting and updating the head
+ * and working towards the tail end. */
+struct lwis_i2c_process_queue {
+ /* Head node for the process queue */
+ struct list_head head;
+ /* Total number of devices that are queued to be processed */
+ int number_of_requests;
+};
+
+/*
+ * struct lwis_i2c_bus_manager
+ * This defines the main attributes for I2C Bus Manager.
+ */
+struct lwis_i2c_bus_manager {
+ /* Unique identifier for this I2C bus manager */
+ int i2c_bus_id;
+ /* Name of I2C Bus manager corresponds to the name of the I2C Bus*/
+ char i2c_bus_name[LWIS_MAX_NAME_STRING_LEN];
+ /* Lock to control access to bus transfers */
+ struct mutex i2c_bus_lock;
+ /* Lock to control access to the I2C process queue for this bus */
+ spinlock_t i2c_process_queue_lock;
+ /* I2C Bus thread priority */
+ u32 i2c_bus_thread_priority;
+ /* Worker thread */
+ struct kthread_worker i2c_bus_worker;
+ struct task_struct *i2c_bus_worker_thread;
+ /* Queue of all I2C devices that have data to transfer in their process queues */
+ struct lwis_i2c_process_queue i2c_bus_process_queue;
+ /* List of I2C devices using this bus */
+ struct list_head i2c_connected_devices;
+ /* Total number of physically connected devices to the bus
+ * This count is set while probe/unprobe sequence */
+ int number_of_connected_devices;
+};
+
+/* This maintains the structure to identify the connected devices to a given I2C bus.
+ * This will be used to guard the bus against processing any illegal device entries */
+struct lwis_i2c_connected_device {
+ struct lwis_device *connected_device;
+ struct list_head connected_device_node;
+};
+
+void lwis_i2c_bus_manager_lock_i2c_bus(struct lwis_device *lwis_dev);
+
+void lwis_i2c_bus_manager_unlock_i2c_bus(struct lwis_device *lwis_dev);
+
+struct lwis_i2c_bus_manager *lwis_i2c_bus_manager_get_manager(struct lwis_device *lwis_dev);
+
+int lwis_i2c_bus_manager_create(struct lwis_i2c_device *i2c_dev);
+
+void lwis_i2c_bus_manager_disconnect(struct lwis_device *lwis_dev);
+
+void lwis_i2c_bus_manager_process_worker_queue(struct lwis_client *client);
+
+int lwis_i2c_bus_manager_enqueue_transfer_request(struct lwis_i2c_bus_manager *i2c_bus_manager,
+ struct lwis_device **lwis_dev);
+
+void lwis_i2c_bus_manager_flush_i2c_worker(struct lwis_device *lwis_dev);
+
+void lwis_i2c_bus_manager_list_initialize(void);
+
+void lwis_i2c_bus_manager_list_deinitialize(void);
+
+#endif /* LWIS_I2C_BUS_MANAGER_H */
diff --git a/lwis_i2c_sched.c b/lwis_i2c_sched.c
new file mode 100644
index 0000000..3c61fcf
--- /dev/null
+++ b/lwis_i2c_sched.c
@@ -0,0 +1,119 @@
+/*
+ * Google LWIS I2C Bus Manager
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#define pr_fmt(fmt) KBUILD_MODNAME "-i2c-sched: " fmt
+
+#include "lwis_i2c_sched.h"
+#include "lwis_i2c_bus_manager.h"
+
+/*
+ * lwis_i2c_process_request_queue_is_empty:
+ * Checks if the I2C process request queue is empty
+*/
+static bool lwis_i2c_process_request_queue_is_empty(struct lwis_i2c_process_queue *process_queue)
+{
+ if ((!process_queue) || ((process_queue) && (process_queue->number_of_requests == 0))) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * lwis_i2c_process_request_queue_initialize:
+ * Initializes the I2C process request queue for a given I2C Bus
+*/
+void lwis_i2c_process_request_queue_initialize(struct lwis_i2c_process_queue *process_queue)
+{
+ process_queue->number_of_requests = 0;
+ INIT_LIST_HEAD(&process_queue->head);
+}
+
+/*
+ * lwis_i2c_process_request_queue_destroy:
+ * Frees all the requests in the queue
+*/
+void lwis_i2c_process_request_queue_destroy(struct lwis_i2c_process_queue *process_queue)
+{
+ struct list_head *request;
+ struct list_head *request_tmp;
+ struct lwis_i2c_process_request *process_request;
+
+ if (!process_queue)
+ return;
+
+ if (lwis_i2c_process_request_queue_is_empty(process_queue))
+ return;
+
+ list_for_each_safe (request, request_tmp, &process_queue->head) {
+ process_request =
+ list_entry(request, struct lwis_i2c_process_request, request_node);
+ list_del(&process_request->request_node);
+ process_request->requesting_device = NULL;
+ kfree(process_request);
+ process_request = NULL;
+ --process_queue->number_of_requests;
+ }
+}
+
+/*
+ * lwis_i2c_process_request_queue_enqueue_request:
+ * Enqueues a requesting device on tail of the I2C Scheduler
+*/
+int lwis_i2c_process_request_queue_enqueue_request(struct lwis_i2c_process_queue *process_queue,
+ struct lwis_device **requesting_device)
+{
+ int ret = 0;
+ struct lwis_i2c_process_request *request;
+ struct lwis_device *lwis_dev = *requesting_device;
+
+ if ((!process_queue) || (!requesting_device) || (!lwis_dev)) {
+ pr_err("Invalid pointer\n");
+ return -EINVAL;
+ }
+
+ // Atomic allocation needed here since this memory is allocated within
+ // transition and periodic io locks of various I2C devices
+ request = kzalloc(sizeof(struct lwis_i2c_process_request), GFP_ATOMIC);
+ if (!request) {
+ dev_err(lwis_dev->dev, "Failed to allocate I2C Process Request Node memory\n");
+ return -ENOMEM;
+ }
+ request->requesting_device = requesting_device;
+ INIT_LIST_HEAD(&request->request_node);
+ list_add_tail(&request->request_node, &process_queue->head);
+ process_queue->number_of_requests++;
+
+ return ret;
+}
+
+/*
+ * lwis_i2c_process_request_queue_dequeue_request:
+ * Dequeues a lwis device from head of the I2C Scheduler
+*/
+struct lwis_device **
+lwis_i2c_process_request_queue_dequeue_request(struct lwis_i2c_process_queue *process_queue)
+{
+ struct lwis_i2c_process_request *request;
+ struct lwis_device **requested_device = NULL;
+
+ if (lwis_i2c_process_request_queue_is_empty(process_queue)) {
+ return requested_device;
+ }
+
+ request = list_first_entry_or_null(&process_queue->head, struct lwis_i2c_process_request,
+ request_node);
+ if (request) {
+ requested_device = request->requesting_device;
+ list_del(&request->request_node);
+ kfree(request);
+ request = NULL;
+ process_queue->number_of_requests--;
+ }
+ return requested_device;
+} \ No newline at end of file
diff --git a/lwis_i2c_sched.h b/lwis_i2c_sched.h
new file mode 100644
index 0000000..de3390d
--- /dev/null
+++ b/lwis_i2c_sched.h
@@ -0,0 +1,37 @@
+/*
+ * Google LWIS I2C Bus Manager
+ *
+ * Copyright (c) 2023 Google, LLC
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef LWIS_I2C_SCHED_H_
+#define LWIS_I2C_SCHED_H_
+
+#include "lwis_device.h"
+
+// Forward declaration
+struct lwis_i2c_process_queue;
+
+/* lwis_i2c_process_request:
+ * This maintains the node to identify the devices that
+ * have a request to be processed on a given I2C bus */
+struct lwis_i2c_process_request {
+ struct lwis_device **requesting_device;
+ struct list_head request_node;
+};
+
+void lwis_i2c_process_request_queue_initialize(struct lwis_i2c_process_queue *process_queue);
+
+void lwis_i2c_process_request_queue_destroy(struct lwis_i2c_process_queue *process_queue);
+
+int lwis_i2c_process_request_queue_enqueue_request(struct lwis_i2c_process_queue *process_queue,
+ struct lwis_device **requesting_device);
+
+struct lwis_device **
+lwis_i2c_process_request_queue_dequeue_request(struct lwis_i2c_process_queue *process_queue);
+
+#endif /* LWIS_I2C_SCHED_H_ */ \ No newline at end of file
diff --git a/lwis_ioctl.c b/lwis_ioctl.c
index 64cb1e2..1599149 100644
--- a/lwis_ioctl.c
+++ b/lwis_ioctl.c
@@ -36,6 +36,7 @@
#include "lwis_regulator.h"
#include "lwis_transaction.h"
#include "lwis_util.h"
+#include "lwis_i2c_bus_manager.h"
#define IOCTL_TO_ENUM(x) _IOC_NR(x)
#define IOCTL_ARG_SIZE(x) _IOC_SIZE(x)
@@ -189,6 +190,7 @@ static int synchronous_process_io_entries(struct lwis_device *lwis_dev, int num_
{
int ret = 0, i = 0;
+ lwis_i2c_bus_manager_lock_i2c_bus(lwis_dev);
/* Use write memory barrier at the beginning of I/O entries if the access protocol
* allows it */
if (lwis_dev->vops.register_io_barrier != NULL) {
@@ -238,6 +240,7 @@ exit:
/*use_read_barrier=*/true,
/*use_write_barrier=*/false);
}
+ lwis_i2c_bus_manager_unlock_i2c_bus(lwis_dev);
return ret;
}
diff --git a/lwis_periodic_io.c b/lwis_periodic_io.c
index 85229bc..b3000f2 100644
--- a/lwis_periodic_io.c
+++ b/lwis_periodic_io.c
@@ -23,6 +23,7 @@
#include "lwis_ioreg.h"
#include "lwis_transaction.h"
#include "lwis_util.h"
+#include "lwis_i2c_bus_manager.h"
static enum hrtimer_restart periodic_io_timer_func(struct hrtimer *timer)
{
@@ -34,10 +35,16 @@ static enum hrtimer_restart periodic_io_timer_func(struct hrtimer *timer)
struct lwis_periodic_io_proxy *periodic_io_proxy;
struct lwis_client *client;
bool active_periodic_io_present = false;
+ struct lwis_device *lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ int ret = 0;
periodic_io_list = container_of(timer, struct lwis_periodic_io_list, hr_timer);
client = periodic_io_list->client;
+ lwis_dev = client->lwis_dev;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
/* Go through all periodic io under the chosen periodic list */
spin_lock_irqsave(&client->periodic_io_lock, flags);
list_for_each_safe (it_period, it_period_tmp, &periodic_io_list->list) {
@@ -47,7 +54,7 @@ static enum hrtimer_restart periodic_io_timer_func(struct hrtimer *timer)
client->lwis_dev, sizeof(*periodic_io_proxy), GFP_ATOMIC);
if (!periodic_io_proxy) {
/* Non-fatal, skip this period */
- pr_warn("Cannot allocate new periodic io proxy.\n");
+ dev_warn(lwis_dev->dev, "Cannot allocate new periodic io proxy.\n");
} else {
periodic_io_proxy->periodic_io = periodic_io;
list_add_tail(&periodic_io_proxy->process_queue_node,
@@ -57,8 +64,17 @@ static enum hrtimer_restart periodic_io_timer_func(struct hrtimer *timer)
}
}
if (active_periodic_io_present) {
- kthread_queue_work(&client->lwis_dev->transaction_worker,
- &client->transaction_work);
+ if (i2c_bus_manager) {
+ ret = lwis_i2c_bus_manager_enqueue_transfer_request(i2c_bus_manager,
+ &client->lwis_dev);
+ if (!ret) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker,
+ &client->i2c_work);
+ }
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
}
spin_unlock_irqrestore(&client->periodic_io_lock, flags);
if (!active_periodic_io_present) {
@@ -89,10 +105,11 @@ static struct lwis_periodic_io_list *periodic_io_list_create_locked(struct lwis_
int64_t period_ns)
{
ktime_t ktime;
+ struct lwis_device *lwis_dev = client->lwis_dev;
struct lwis_periodic_io_list *periodic_io_list =
kmalloc(sizeof(struct lwis_periodic_io_list), GFP_ATOMIC);
if (!periodic_io_list) {
- pr_err("Cannot allocate new event list\n");
+ dev_err(lwis_dev->dev, "Cannot allocate new event list\n");
return NULL;
}
@@ -104,7 +121,7 @@ static struct lwis_periodic_io_list *periodic_io_list_create_locked(struct lwis_
* into the client timer list */
INIT_LIST_HEAD(&periodic_io_list->list);
hash_add(client->timer_list, &periodic_io_list->node, period_ns);
- pr_info("Created hrtimer with timeout time %lldns", period_ns);
+ dev_info(lwis_dev->dev, "Created hrtimer with timeout time %lldns", period_ns);
/* Initialize and start the hrtimer for this periodic io list */
hrtimer_init(&periodic_io_list->hr_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
@@ -184,6 +201,7 @@ static int process_io_entries(struct lwis_client *client,
}
reinit_completion(&periodic_io->io_done);
+ lwis_i2c_bus_manager_lock_i2c_bus(lwis_dev);
for (i = 0; i < info->num_io_entries; ++i) {
/* Abort if periodic io is deactivated during processing.
* Abort can only apply to <= 1 write entries to prevent partial writes,
@@ -267,6 +285,8 @@ static int process_io_entries(struct lwis_client *client,
event_push:
complete(&periodic_io->io_done);
+ lwis_i2c_bus_manager_unlock_i2c_bus(lwis_dev);
+
/* Use read memory barrier at the beginning of I/O entries if the access protocol
* allows it */
if (lwis_dev->vops.register_io_barrier != NULL) {
@@ -280,8 +300,10 @@ event_push:
* there is an error */
if (!pending_events) {
if (resp->error_code && resp->error_code != -ECANCELED) {
- pr_err("process_io_entries fails with error code %d, periodic io %lld, io_entries[%d], entry_type %d",
- resp->error_code, info->id, i, entry->type);
+ dev_info(
+ lwis_dev->dev,
+ "process_io_entries fails with error code %d, periodic io %lld, io_entries[%d], entry_type %d",
+ resp->error_code, info->id, i, entry->type);
}
return ret;
}
@@ -323,20 +345,26 @@ void lwis_process_periodic_io_in_queue(struct lwis_client *client)
list_for_each_safe (it_period, it_period_tmp, &client->periodic_io_process_queue) {
periodic_io_proxy =
list_entry(it_period, struct lwis_periodic_io_proxy, process_queue_node);
- periodic_io = periodic_io_proxy->periodic_io;
- list_del(&periodic_io_proxy->process_queue_node);
- /* Error indicates the cancellation of the periodic io */
- if (periodic_io->resp->error_code || !periodic_io->active) {
- error_code = periodic_io->resp->error_code ? periodic_io->resp->error_code :
- -ECANCELED;
- push_periodic_io_error_event_locked(periodic_io, error_code,
- &pending_events);
- } else {
- spin_unlock_irqrestore(&client->periodic_io_lock, flags);
- process_io_entries(client, periodic_io_proxy, &pending_events);
- spin_lock_irqsave(&client->periodic_io_lock, flags);
+ if (periodic_io_proxy) {
+ periodic_io = periodic_io_proxy->periodic_io;
+ list_del(&periodic_io_proxy->process_queue_node);
+ if (periodic_io) {
+ /* Error indicates the cancellation of the periodic io */
+ if (periodic_io->resp->error_code || !periodic_io->active) {
+ error_code = periodic_io->resp->error_code ?
+ periodic_io->resp->error_code :
+ -ECANCELED;
+ push_periodic_io_error_event_locked(periodic_io, error_code,
+ &pending_events);
+ } else {
+ spin_unlock_irqrestore(&client->periodic_io_lock, flags);
+ process_io_entries(client, periodic_io_proxy,
+ &pending_events);
+ spin_lock_irqsave(&client->periodic_io_lock, flags);
+ }
+ }
+ lwis_allocator_free(client->lwis_dev, periodic_io_proxy);
}
- lwis_allocator_free(client->lwis_dev, periodic_io_proxy);
}
spin_unlock_irqrestore(&client->periodic_io_lock, flags);
lwis_pending_events_emit(client->lwis_dev, &pending_events);
@@ -507,6 +535,15 @@ int lwis_periodic_io_client_flush(struct lwis_client *client)
struct lwis_periodic_io_list *it_periodic_io_list;
unsigned long flags;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+
+ struct lwis_periodic_io *periodic_cleanup_io;
+ struct lwis_periodic_io_proxy *periodic_cleanup_io_proxy;
+ struct list_head *it_cleanup_period, *it_cleanup_period_tmp;
+
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
/* First, cancel all timers */
hash_for_each_safe (client->timer_list, i, tmp, it_periodic_io_list, node) {
spin_lock_irqsave(&client->periodic_io_lock, flags);
@@ -521,11 +558,30 @@ int lwis_periodic_io_client_flush(struct lwis_client *client)
}
/* Wait until all workload in process queue are processed */
- if (client->lwis_dev->transaction_worker_thread) {
- kthread_flush_worker(&client->lwis_dev->transaction_worker);
+ if (i2c_bus_manager) {
+ lwis_i2c_bus_manager_flush_i2c_worker(lwis_dev);
+ } else {
+ if (client->lwis_dev->transaction_worker_thread) {
+ kthread_flush_worker(&client->lwis_dev->transaction_worker);
+ }
}
spin_lock_irqsave(&client->periodic_io_lock, flags);
+ /* Cleanup any stale entries remaining after the flush */
+ list_for_each_safe (it_cleanup_period, it_cleanup_period_tmp,
+ &client->periodic_io_process_queue) {
+ periodic_cleanup_io_proxy = list_entry(
+ it_cleanup_period, struct lwis_periodic_io_proxy, process_queue_node);
+ if (periodic_cleanup_io_proxy) {
+ periodic_cleanup_io = periodic_cleanup_io_proxy->periodic_io;
+ list_del(&periodic_cleanup_io_proxy->process_queue_node);
+ if (periodic_cleanup_io) {
+ periodic_cleanup_io->active = false;
+ }
+ lwis_allocator_free(client->lwis_dev, periodic_cleanup_io_proxy);
+ }
+ }
+
/* Release the periodic io list of from all timers */
hash_for_each_safe (client->timer_list, i, tmp, it_periodic_io_list, node) {
list_for_each_safe (it_period, it_period_tmp, &it_periodic_io_list->list) {
@@ -535,6 +591,7 @@ int lwis_periodic_io_client_flush(struct lwis_client *client)
lwis_periodic_io_free(client->lwis_dev, periodic_io);
}
}
+
spin_unlock_irqrestore(&client->periodic_io_lock, flags);
return 0;
}
@@ -548,7 +605,8 @@ int lwis_periodic_io_client_cleanup(struct lwis_client *client)
ret = lwis_periodic_io_client_flush(client);
if (ret) {
- pr_err("Failed to wait for all in-process periodic io to complete\n");
+ dev_err(client->lwis_dev->dev,
+ "Failed to wait for all in-process periodic io to complete\n");
return ret;
}
diff --git a/lwis_transaction.c b/lwis_transaction.c
index 3212623..655b9e8 100644
--- a/lwis_transaction.c
+++ b/lwis_transaction.c
@@ -27,6 +27,7 @@
#include "lwis_io_entry.h"
#include "lwis_ioreg.h"
#include "lwis_util.h"
+#include "lwis_i2c_bus_manager.h"
#define CREATE_TRACE_POINTS
#include "lwis_trace.h"
@@ -186,7 +187,7 @@ static int process_transaction(struct lwis_client *client, struct lwis_transacti
/*use_read_barrier=*/false,
/*use_write_barrier=*/true);
}
-
+ lwis_i2c_bus_manager_lock_i2c_bus(lwis_dev);
for (i = 0; i < info->num_io_entries; ++i) {
entry = &info->io_entries[i];
if (entry->type == LWIS_IO_ENTRY_WRITE ||
@@ -311,6 +312,7 @@ static int process_transaction(struct lwis_client *client, struct lwis_transacti
}
resp->completion_index = i;
}
+ lwis_i2c_bus_manager_unlock_i2c_bus(lwis_dev);
if (lwis_transaction_debug) {
process_duration_ns = ktime_to_ns(lwis_get_time() - process_timestamp);
@@ -465,12 +467,17 @@ int lwis_transaction_client_flush(struct lwis_client *client)
int i;
struct hlist_node *tmp;
struct lwis_transaction_event_list *it_evt_list;
+ struct lwis_device *lwis_dev = NULL;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
if (!client) {
pr_err("Client pointer cannot be NULL while flushing transactions.\n");
return -ENODEV;
}
+ lwis_dev = client->lwis_dev;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
spin_lock_irqsave(&client->transaction_lock, flags);
hash_for_each_safe (client->transaction_list, i, tmp, it_evt_list, node) {
if ((it_evt_list->event_id & 0xFFFF0000FFFFFFFFll) ==
@@ -491,8 +498,13 @@ int lwis_transaction_client_flush(struct lwis_client *client)
}
spin_unlock_irqrestore(&client->transaction_lock, flags);
- if (client->lwis_dev->transaction_worker_thread)
- kthread_flush_worker(&client->lwis_dev->transaction_worker);
+ if (i2c_bus_manager) {
+ lwis_i2c_bus_manager_flush_i2c_worker(lwis_dev);
+ } else {
+ if (client->lwis_dev->transaction_worker_thread) {
+ kthread_flush_worker(&client->lwis_dev->transaction_worker);
+ }
+ }
spin_lock_irqsave(&client->transaction_lock, flags);
/* The transaction queue should be empty after canceling all transactions,
@@ -725,12 +737,24 @@ static int queue_transaction_locked(struct lwis_client *client,
{
struct lwis_transaction_event_list *event_list;
struct lwis_transaction_info_v2 *info = &transaction->info;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
if (transaction->queue_immediately) {
/* Immediate trigger. */
list_add_tail(&transaction->process_queue_node, &client->transaction_process_queue);
- kthread_queue_work(&client->lwis_dev->transaction_worker,
- &client->transaction_work);
+ if (i2c_bus_manager) {
+ if (!lwis_i2c_bus_manager_enqueue_transfer_request(i2c_bus_manager,
+ &client->lwis_dev)) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker,
+ &client->i2c_work);
+ } else {
+ dev_err(client->lwis_dev->dev, "Cannot queue I2C transfer\n");
+ }
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
} else if (lwis_triggered_by_condition(transaction)) {
/* Trigger by trigger conditions. */
add_pending_transaction(client, transaction);
@@ -845,6 +869,8 @@ int lwis_transaction_event_trigger(struct lwis_client *client, int64_t event_id,
struct lwis_transaction *new_instance;
int64_t trigger_counter = 0;
struct list_head pending_fences;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
INIT_LIST_HEAD(&pending_fences);
@@ -920,8 +946,18 @@ int lwis_transaction_event_trigger(struct lwis_client *client, int64_t event_id,
/* Schedule deferred transactions */
if (!list_empty(&client->transaction_process_queue)) {
- kthread_queue_work(&client->lwis_dev->transaction_worker,
- &client->transaction_work);
+ if (i2c_bus_manager) {
+ if (!lwis_i2c_bus_manager_enqueue_transfer_request(i2c_bus_manager,
+ &client->lwis_dev)) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker,
+ &client->i2c_work);
+ } else {
+ dev_err(client->lwis_dev->dev, "Cannot queue I2C transfer\n");
+ }
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
}
spin_unlock_irqrestore(&client->transaction_lock, flags);
@@ -940,6 +976,8 @@ void lwis_transaction_fence_trigger(struct lwis_client *client, struct lwis_fenc
struct list_head *it_tran, *it_tran_tmp;
struct list_head pending_events;
struct list_head pending_fences;
+ struct lwis_device *lwis_dev = client->lwis_dev;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
if (list_empty(transaction_list)) {
return;
@@ -987,8 +1025,18 @@ void lwis_transaction_fence_trigger(struct lwis_client *client, struct lwis_fenc
/* Schedule deferred transactions */
if (!list_empty(&client->transaction_process_queue)) {
- kthread_queue_work(&client->lwis_dev->transaction_worker,
- &client->transaction_work);
+ if (i2c_bus_manager) {
+ if (!lwis_i2c_bus_manager_enqueue_transfer_request(i2c_bus_manager,
+ &client->lwis_dev)) {
+ kthread_queue_work(&i2c_bus_manager->i2c_bus_worker,
+ &client->i2c_work);
+ } else {
+ dev_err(client->lwis_dev->dev, "Cannot queue I2C transfer\n");
+ }
+ } else {
+ kthread_queue_work(&client->lwis_dev->transaction_worker,
+ &client->transaction_work);
+ }
}
spin_unlock_irqrestore(&client->transaction_lock, flags);
diff --git a/lwis_util.c b/lwis_util.c
index bb6e211..5a440d2 100644
--- a/lwis_util.c
+++ b/lwis_util.c
@@ -161,4 +161,9 @@ int lwis_set_kthread_priority(struct lwis_device *lwis_dev, struct task_struct *
}
return 0;
+}
+
+bool lwis_check_device_type(struct lwis_device *lwis_dev, int32_t type)
+{
+ return ((lwis_dev) && (lwis_dev->type == type));
} \ No newline at end of file
diff --git a/lwis_util.h b/lwis_util.h
index 29dca82..a313c0c 100644
--- a/lwis_util.h
+++ b/lwis_util.h
@@ -80,4 +80,9 @@ int lwis_create_kthread_workers(struct lwis_device *lwis_dev);
*/
int lwis_set_kthread_priority(struct lwis_device *lwis_dev, struct task_struct *task, u32 priority);
+/*
+ * lwis_check_device_type: Returns true if the passed lwis_device's type is same as 'type'
+ */
+bool lwis_check_device_type(struct lwis_device *lwis_dev, int32_t type);
+
#endif // LWIS_UTIL_H_