summaryrefslogtreecommitdiff
path: root/lwis_i2c_bus_manager.c
diff options
context:
space:
mode:
Diffstat (limited to 'lwis_i2c_bus_manager.c')
-rw-r--r--lwis_i2c_bus_manager.c751
1 files changed, 751 insertions, 0 deletions
diff --git a/lwis_i2c_bus_manager.c b/lwis_i2c_bus_manager.c
new file mode 100644
index 0000000..c030f27
--- /dev/null
+++ b/lwis_i2c_bus_manager.c
@@ -0,0 +1,751 @@
+/*
+ * 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"
+
+bool lwis_i2c_bus_manager_debug;
+module_param(lwis_i2c_bus_manager_debug, bool, 0644);
+
+/* 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)
+{
+ int i = 0;
+ if (!i2c_bus_manager) {
+ return;
+ }
+
+ dev_info(lwis_dev->dev, "Destroying I2C Bus Manager: %s\n", i2c_bus_manager->i2c_bus_name);
+ mutex_lock(&i2c_bus_manager->i2c_process_queue_lock);
+ for (i = 0; i < I2C_MAX_PRIORITY_LEVELS; i++) {
+ lwis_i2c_process_request_queue_destroy(&i2c_bus_manager->i2c_bus_process_queue[i]);
+ }
+ mutex_unlock(&i2c_bus_manager->i2c_process_queue_lock);
+
+ /* 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;
+}
+
+static bool i2c_device_priority_is_valid(int device_priority)
+{
+ if ((device_priority >= I2C_DEVICE_HIGH_PRIORITY) &&
+ (device_priority <= I2C_DEVICE_LOW_PRIORITY)) {
+ return true;
+ }
+ return false;
+}
+
+/*
+ * lwis_i2c_bus_manager_process_worker_queue:
+ * Function to be called by i2c bus manager worker thread to
+ * pick the next I2C client that is scheduled for transfer.
+ * The process queue will be processed in order of I2C
+ * device priority.
+ */
+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;
+ int i = 0;
+
+ /* The transfers will be processed in fifo order */
+ struct lwis_client *client_to_process = NULL;
+ struct lwis_device *lwis_dev_to_process = NULL;
+ struct lwis_i2c_process_queue *process_queue = NULL;
+ struct lwis_i2c_process_request *process_request = NULL;
+
+ struct list_head *i2c_client_node, *i2c_client_tmp_node;
+
+ lwis_dev = client->lwis_dev;
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "%s scheduled by %s\n", i2c_bus_manager->i2c_bus_name,
+ lwis_dev->name);
+ }
+
+ if (!i2c_bus_manager) {
+ dev_err(lwis_dev->dev, "I2C Bus Manager is null\n");
+ return;
+ }
+
+ mutex_lock(&i2c_bus_manager->i2c_process_queue_lock);
+ for (i = 0; i < I2C_MAX_PRIORITY_LEVELS; i++) {
+ process_queue = &i2c_bus_manager->i2c_bus_process_queue[i];
+ list_for_each_safe (i2c_client_node, i2c_client_tmp_node, &process_queue->head) {
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev,
+ "Process request nodes for %s: cur %p tmp %p\n",
+ i2c_bus_manager->i2c_bus_name, i2c_client_node,
+ i2c_client_tmp_node);
+ }
+ process_request = list_entry(i2c_client_node,
+ struct lwis_i2c_process_request, request_node);
+ if (!process_request) {
+ dev_err(lwis_dev->dev, "I2C Bus Worker process_request is null\n");
+ break;
+ }
+
+ client_to_process = process_request->requesting_client;
+ if (!client_to_process) {
+ dev_err(lwis_dev->dev,
+ "I2C Bus Worker client_to_process is null\n");
+ break;
+ }
+
+ lwis_dev_to_process = client_to_process->lwis_dev;
+ if (!lwis_dev_to_process) {
+ dev_err(lwis_dev->dev,
+ "I2C Bus Worker lwis_dev_to_process is null\n");
+ break;
+ }
+
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev_to_process->dev, "Processing client start %s\n",
+ lwis_dev_to_process->name);
+ }
+
+ if (is_valid_connected_device(lwis_dev_to_process, i2c_bus_manager)) {
+ lwis_process_transactions_in_queue(client_to_process);
+ lwis_process_periodic_io_in_queue(client_to_process);
+ }
+
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev_to_process->dev, "Processing client end %s\n",
+ lwis_dev_to_process->name);
+ }
+ }
+ }
+ mutex_unlock(&i2c_bus_manager->i2c_process_queue_lock);
+}
+
+/*
+ * 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;
+ int i = 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);
+ mutex_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 */
+ for (i = 0; i < I2C_MAX_PRIORITY_LEVELS; i++) {
+ lwis_i2c_process_request_queue_initialize(
+ &i2c_bus_manager->i2c_bus_process_queue[i]);
+ }
+
+ /* 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_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);
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "%s lock\n", i2c_bus_manager->i2c_bus_name);
+ }
+ }
+}
+
+/* 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) {
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "%s unlock\n", i2c_bus_manager->i2c_bus_name);
+ }
+ 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;
+}
+
+/* lwis_i2c_bus_manager_flush_i2c_worker:
+ * Flushes the I2C Bus Manager worker
+ */
+void lwis_i2c_bus_manager_flush_i2c_worker(struct lwis_device *lwis_dev)
+{
+ 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);
+}
+
+/* lwis_i2c_bus_manager_list_initialize:
+ * Initializes bus manager global list. This is the list that holds
+ * actual bus manager pointers for a given physical I2C Bus connection
+ */
+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);
+}
+
+/* lwis_i2c_bus_manager_list_deinitialize:
+ * Deinitializes bus manager global list
+ */
+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);
+}
+
+/* lwis_i2c_bus_manager_connect_client:
+ * Connects a lwis client to the bus manager to be processed by the worker.
+ * The client will be connected to the appropriate priority queue based
+ * on the I2C device priority specified in the dts for the I2C device node.
+ * I2C lwis client is always connected when a new instance of client is
+ * created.
+ */
+int lwis_i2c_bus_manager_connect_client(struct lwis_client *connecting_client)
+{
+ int ret = 0;
+ int device_priority = I2C_MAX_PRIORITY_LEVELS;
+ bool create_client_node = true;
+ struct lwis_i2c_process_request *i2c_connecting_client_node;
+ struct lwis_device *lwis_dev = NULL;
+ struct lwis_i2c_process_queue *process_queue = NULL;
+ struct lwis_i2c_device *i2c_dev = NULL;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+ struct list_head *request, *request_tmp;
+ struct lwis_i2c_process_request *search_node;
+
+ if (!connecting_client) {
+ pr_err("Connecting client pointer for I2C Bus Manager is NULL\n");
+ return -EINVAL;
+ }
+
+ lwis_dev = connecting_client->lwis_dev;
+ if (!lwis_dev) {
+ pr_err("Connecting device for I2C Bus Manager is NULL\n");
+ return -EINVAL;
+ }
+
+ if (!lwis_check_device_type(lwis_dev, DEVICE_TYPE_I2C)) {
+ return ret;
+ }
+
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (!i2c_bus_manager) {
+ dev_err(lwis_dev->dev, "I2C bus manager is NULL\n");
+ return -EINVAL;
+ }
+
+ i2c_dev = container_of(lwis_dev, struct lwis_i2c_device, base_dev);
+ if (!i2c_dev) {
+ dev_err(lwis_dev->dev, "I2C device is NULL\n");
+ return -EINVAL;
+ }
+
+ device_priority = i2c_dev->device_priority;
+ if (!i2c_device_priority_is_valid(device_priority)) {
+ dev_err(lwis_dev->dev, "Invalid I2C device priority %d\n", device_priority);
+ return -EINVAL;
+ }
+
+ mutex_lock(&i2c_bus_manager->i2c_process_queue_lock);
+
+ // Search for existing client node in the queue, if client is already connected
+ // to this bus then don't create a new client node
+ process_queue = &i2c_bus_manager->i2c_bus_process_queue[device_priority];
+ if (!lwis_i2c_process_request_queue_is_empty(process_queue)) {
+ list_for_each_safe (request, request_tmp, &process_queue->head) {
+ search_node =
+ list_entry(request, struct lwis_i2c_process_request, request_node);
+ if (search_node->requesting_client == connecting_client) {
+ dev_info(lwis_dev->dev,
+ "I2C client already connected %s(%p) to bus %s \n",
+ lwis_dev->name, connecting_client,
+ i2c_bus_manager->i2c_bus_name);
+ create_client_node = false;
+ break;
+ }
+ }
+ }
+
+ if (create_client_node) {
+ i2c_connecting_client_node =
+ kzalloc(sizeof(struct lwis_i2c_process_request), GFP_KERNEL);
+ if (!i2c_connecting_client_node) {
+ dev_err(lwis_dev->dev, "Failed to connect client to I2C Bus Manager\n");
+ return -ENOMEM;
+ }
+ i2c_connecting_client_node->requesting_client = connecting_client;
+ INIT_LIST_HEAD(&i2c_connecting_client_node->request_node);
+ list_add_tail(&i2c_connecting_client_node->request_node, &process_queue->head);
+ ++process_queue->number_of_nodes;
+ dev_info(lwis_dev->dev, "Connecting client %s(%p) to bus %s\n", lwis_dev->name,
+ connecting_client, i2c_bus_manager->i2c_bus_name);
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "Adding process request %p\n",
+ i2c_connecting_client_node);
+ }
+ }
+
+ mutex_unlock(&i2c_bus_manager->i2c_process_queue_lock);
+ return ret;
+}
+
+/* lwis_i2c_bus_manager_disconnect_client:
+ * Disconnects a lwis client to the bus manager. This will make sure that
+ * the released client is not processed further by the I2C worker.
+ * The client will be disconnected from the appropriate priority queue based
+ * on the I2C device priority specified in the dts for the I2C device node.
+ * I2C lwis client is always disconnected when the instance of client is
+ * released/destroyed.
+ */
+void lwis_i2c_bus_manager_disconnect_client(struct lwis_client *disconnecting_client)
+{
+ int device_priority = I2C_MAX_PRIORITY_LEVELS;
+ struct lwis_i2c_process_request *i2c_disconnecting_client_node;
+ struct lwis_device *lwis_dev = NULL;
+ struct lwis_i2c_process_queue *process_queue = NULL;
+ struct lwis_i2c_device *i2c_dev = NULL;
+ struct list_head *request, *request_tmp;
+ struct lwis_i2c_bus_manager *i2c_bus_manager = NULL;
+
+ if (!disconnecting_client) {
+ pr_err("Disconnecting client pointer for I2C Bus Manager is NULL\n");
+ return;
+ }
+
+ lwis_dev = disconnecting_client->lwis_dev;
+ if (!lwis_dev) {
+ pr_err("Disconnecting device for I2C Bus Manager is NULL\n");
+ return;
+ }
+
+ if (!lwis_check_device_type(lwis_dev, DEVICE_TYPE_I2C)) {
+ return;
+ }
+
+ i2c_bus_manager = lwis_i2c_bus_manager_get_manager(lwis_dev);
+ if (!i2c_bus_manager) {
+ dev_err(lwis_dev->dev, "I2C bus manager is NULL\n");
+ return;
+ }
+
+ i2c_dev = container_of(lwis_dev, struct lwis_i2c_device, base_dev);
+ if (!i2c_dev) {
+ dev_err(lwis_dev->dev, "I2C device is NULL\n");
+ return;
+ }
+
+ device_priority = i2c_dev->device_priority;
+ if (!i2c_device_priority_is_valid(device_priority)) {
+ dev_err(lwis_dev->dev, "Invalid I2C device priority %d\n", device_priority);
+ return;
+ }
+
+ mutex_lock(&i2c_bus_manager->i2c_process_queue_lock);
+ process_queue = &i2c_bus_manager->i2c_bus_process_queue[device_priority];
+ list_for_each_safe (request, request_tmp, &process_queue->head) {
+ i2c_disconnecting_client_node =
+ list_entry(request, struct lwis_i2c_process_request, request_node);
+ if (i2c_disconnecting_client_node->requesting_client == disconnecting_client) {
+ dev_info(lwis_dev->dev, "Disconnecting I2C client %s(%p) from bus %s\n",
+ lwis_dev->name, disconnecting_client,
+ i2c_bus_manager->i2c_bus_name);
+ list_del(&i2c_disconnecting_client_node->request_node);
+ i2c_disconnecting_client_node->requesting_client = NULL;
+ if (lwis_i2c_bus_manager_debug) {
+ dev_info(lwis_dev->dev, "Freeing process request %p\n",
+ i2c_disconnecting_client_node);
+ }
+ kfree(i2c_disconnecting_client_node);
+ i2c_disconnecting_client_node = NULL;
+ --process_queue->number_of_nodes;
+ break;
+ }
+ }
+ mutex_unlock(&i2c_bus_manager->i2c_process_queue_lock);
+} \ No newline at end of file