From 9cb7cb85f59509ac445116e9458c502cf6cb74e6 Mon Sep 17 00:00:00 2001
From: Christian Gromm <christian.gromm@microchip.com>
Date: Thu, 9 Nov 2017 13:20:23 +0100
Subject: [PATCH 2/2] src: most: add auto conf feature

This patch adds the auto configuration feature to the driver
sources. It is needed to have the driver configured automatically
upon start up w/o the need for userspace to set up sysfs.

Signed-off-by: Christian Gromm <christian.gromm@microchip.com>
---
 driver/Makefile           |   3 +
 driver/default_conf.c     | 162 ++++++++++++++++++++++++++++++++++++++++++++++
 driver/include/mostcore.h |  64 ++++++++++++++++++
 driver/mostcore/core.c    | 120 ++++++++++++++++++++++++++++------
 4 files changed, 331 insertions(+), 18 deletions(-)
 create mode 100644 driver/default_conf.c

diff --git a/Makefile b/Makefile
index e77a4b6..6d74ebe 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,9 @@ obj-m := mostcore.o
 mostcore-y := mostcore/core.o
 CFLAGS_core.o := -I$(src)/include/
 
+obj-m += default_conf.o
+CFLAGL_default_conf.o := -I$(src)/include
+
 obj-m += aim_cdev.o
 aim_cdev-y := aim-cdev/cdev.o
 CFLAGS_cdev.o := -I$(src)/include/
diff --git a/default_conf.c b/default_conf.c
new file mode 100644
index 0000000..adb1786
--- /dev/null
+++ b/default_conf.c
@@ -0,0 +1,162 @@
+/*
+ * default_conf.c - Default configuration for the MOST channels.
+ *
+ * Copyright (C) 2017, Microchip Technology Germany II GmbH & Co. KG
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * This file is licensed under GPLv2.
+ */
+
+#include "include/mostcore.h"
+#include <linux/module.h>
+
+static struct most_config_probe config_probes[] = {
+
+	/* OS81118 Control */
+	{
+		.ch_name = "ep8f",
+		.cfg = {
+			.direction = MOST_CH_RX,
+			.data_type = MOST_CH_CONTROL,
+			.num_buffers = 16,
+			.buffer_size = 64,
+		},
+		.aim_name = "cdev",
+		.aim_param = "inic-usb-crx",
+	},
+	{
+		.ch_name = "ep0f",
+		.cfg = {
+			.direction = MOST_CH_TX,
+			.data_type = MOST_CH_CONTROL,
+			.num_buffers = 16,
+			.buffer_size = 64,
+		},
+		.aim_name = "cdev",
+		.aim_param = "inic-usb-ctx",
+	},
+	/* OS81118 Async */
+	{
+		.ch_name = "ep8e",
+		.cfg = {
+			.direction = MOST_CH_RX,
+			.data_type = MOST_CH_ASYNC,
+			.num_buffers = 20,
+			.buffer_size = 1522,
+		},
+		.aim_name = "networking",
+		.aim_param = "inic-usb-arx",
+	},
+	{
+		.ch_name = "ep0e",
+		.cfg = {
+			.direction = MOST_CH_TX,
+			.data_type = MOST_CH_ASYNC,
+			.num_buffers = 20,
+			.buffer_size = 1522,
+		},
+		.aim_name = "networking",
+		.aim_param = "inic-usb-atx",
+	},
+	/* OS81210 Control */
+	{
+		.ch_name = "ep87",
+		.cfg = {
+			.direction = MOST_CH_RX,
+			.data_type = MOST_CH_CONTROL,
+			.num_buffers = 16,
+			.buffer_size = 64,
+		},
+		.aim_name = "cdev",
+		.aim_param = "inic-usb-crx",
+	},
+	{
+		.ch_name = "ep07",
+		.cfg = {
+			.direction = MOST_CH_TX,
+			.data_type = MOST_CH_CONTROL,
+			.num_buffers = 16,
+			.buffer_size = 64,
+		},
+		.aim_name = "cdev",
+		.aim_param = "inic-usb-ctx",
+	},
+	/* OS81210 Async */
+	{
+		.ch_name = "ep86",
+		.cfg = {
+			.direction = MOST_CH_RX,
+			.data_type = MOST_CH_ASYNC,
+			.num_buffers = 20,
+			.buffer_size = 1522,
+		},
+		.aim_name = "networking",
+		.aim_param = "inic-usb-arx",
+	},
+	{
+		.ch_name = "ep06",
+		.cfg = {
+			.direction = MOST_CH_TX,
+			.data_type = MOST_CH_ASYNC,
+			.num_buffers = 20,
+			.buffer_size = 1522,
+		},
+		.aim_name = "networking",
+		.aim_param = "inic-usb-atx",
+	},
+	/* Streaming channels (common for all INICs) */
+	{
+		.ch_name = "ep01",
+		.cfg = {
+			.direction = MOST_CH_TX,
+			.data_type = MOST_CH_SYNC,
+			.num_buffers = 8,
+			.buffer_size = 2 * 12 * 42,
+			.subbuffer_size = 12,
+			.packets_per_xact = 42,
+		},
+		.aim_name = "sound",
+		.aim_param = "ep01-6ch.6x16",
+	},
+	{
+		.ch_name = "ep02",
+		.cfg = {
+			.direction = MOST_CH_TX,
+			.data_type = MOST_CH_ISOC,
+			.num_buffers = 8,
+			.buffer_size = 40 * 188,
+			.subbuffer_size = 188,
+			.packets_per_xact = 2,
+		},
+		.aim_name = "cdev",
+		.aim_param = "inic-usb-itx1",
+	},
+	
+	/* sentinel */
+	{}
+};
+
+static struct most_config_set config_set = {
+	.probes = config_probes
+};
+
+static int __init mod_init(void)
+{
+	most_register_config_set(&config_set);
+	return 0;
+}
+
+static void __exit mod_exit(void)
+{
+	most_deregister_config_set(&config_set);
+}
+
+module_init(mod_init);
+module_exit(mod_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Andrey Shvetsov <andrey.shvetsov@k2l.de>");
+MODULE_DESCRIPTION("Default configuration for the MOST channels");
diff --git a/include/mostcore.h b/include/mostcore.h
index dc87121..3c00efb 100644
--- a/include/mostcore.h
+++ b/include/mostcore.h
@@ -145,6 +145,39 @@ struct most_channel_config {
 	u16 dbr_size;
 };
 
+/**
+ * struct most_config_probe - matching rule, channel configuration and
+ *     the optional AIM name used for the automatic configuration and linking
+ *     of the channel
+ * @dev_name: optional matching device id
+ *     ("usb_device 1-1:1.0," "dim2-12345678", etc.)
+ * @ch_name: matching channel name ("ep8f", "ca2", etc.)
+ * @cfg: configuration that will be applied for the found channel
+ * @aim_name: optional name of the AIM that will be linked to the channel
+ *     ("cdev", "networking", "v4l", "sound")
+ * @aim_param: AIM dependent parameter (it is the character device name
+ *     for the cdev AIM, PCM format for the audio AIM, etc.)
+ */
+struct most_config_probe {
+	const char *dev_name;
+	const char *ch_name;
+	struct most_channel_config cfg;
+	const char *aim_name;
+	const char *aim_param;
+};
+
+/**
+ * struct most_config_set - the configuration set containing
+ *     several automatic configurations for the different channels
+ * @probes: list of the matching rules and the configurations,
+ *     that must be ended with the empty structure
+ * @list: list head used by the MostCore
+ */
+struct most_config_set {
+	const struct most_config_probe *probes;
+	struct list_head list;
+};
+
 /*
  * struct mbo - MOST Buffer Object.
  * @context: context for core completion handler
@@ -285,6 +318,37 @@ struct most_aim {
 };
 
 /**
+ * most_register_config_set - registers the configuration set
+ *
+ * @cfg_set: configuration set to be registered for the future probes
+ *
+ * The function registers the given configuration set.
+ *
+ * It is possible to register or deregister several configuration sets
+ * independently.  Different configuration sets may contain the
+ * overlapped matching rules but later registered configuration set has
+ * the higher priority over the prior registered set.
+ *
+ * The only the first matched configuration is applied for each
+ * channel.
+ *
+ * The configuration for the channel is applied at the time of
+ * registration of the parent most_interface.
+ */
+void most_register_config_set(struct most_config_set *cfg_set);
+
+/**
+ * most_deregister_config_set - deregisters the prior registered
+ *     configuration set
+ *
+ * @cfg_set: configuration set to be deregistered
+ *
+ * The calling of this function does not change the current
+ * configuration of the channels.
+ */
+void most_deregister_config_set(struct most_config_set *cfg_set);
+
+/**
  * most_register_interface - Registers instance of the interface.
  * @iface: Pointer to the interface instance description.
  *
diff --git a/mostcore/core.c b/mostcore/core.c
index 9e0a352..6035cf0 100644
--- a/mostcore/core.c
+++ b/mostcore/core.c
@@ -36,6 +36,8 @@ static struct class *most_class;
 static struct device *core_dev;
 static struct ida mdev_id;
 static int dummy_num_buffers;
+static struct list_head config_probes;
+struct mutex config_probes_mt; /* config_probes */
 
 struct most_c_aim_obj {
 	struct most_aim *ptr;
@@ -918,6 +920,30 @@ most_c_obj *get_channel_by_name(char *mdev, char *mdev_ch)
 	return c;
 }
 
+static int link_channel_to_aim(struct most_c_obj *c, struct most_aim *aim,
+			       char *aim_param)
+{
+	int ret;
+	struct most_aim **aim_ptr;
+
+	if (!c->aim0.ptr)
+		aim_ptr = &c->aim0.ptr;
+	else if (!c->aim1.ptr)
+		aim_ptr = &c->aim1.ptr;
+	else
+		return -ENOSPC;
+
+	*aim_ptr = aim;
+	ret = aim->probe_channel(c->iface, c->channel_id,
+				 &c->cfg, &c->kobj, aim_param);
+	if (ret) {
+		*aim_ptr = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
 /**
  * add_link_store - store() function for add_link attribute
  * @aim_obj: pointer to AIM object
@@ -946,45 +972,33 @@ static ssize_t add_link_store(struct most_aim_obj *aim_obj,
 			      size_t len)
 {
 	struct most_c_obj *c;
-	struct most_aim **aim_ptr;
 	char buffer[STRING_SIZE];
 	char *mdev;
 	char *mdev_ch;
-	char *mdev_devnod;
+	char *aim_param;
 	char devnod_buf[STRING_SIZE];
 	int ret;
 	size_t max_len = min_t(size_t, len + 1, STRING_SIZE);
 
 	strlcpy(buffer, buf, max_len);
 
-	ret = split_string(buffer, &mdev, &mdev_ch, &mdev_devnod);
+	ret = split_string(buffer, &mdev, &mdev_ch, &aim_param);
 	if (ret)
 		return ret;
 
-	if (!mdev_devnod || *mdev_devnod == 0) {
+	if (!aim_param || *aim_param == 0) {
 		snprintf(devnod_buf, sizeof(devnod_buf), "%s-%s", mdev,
 			 mdev_ch);
-		mdev_devnod = devnod_buf;
+		aim_param = devnod_buf;
 	}
 
 	c = get_channel_by_name(mdev, mdev_ch);
 	if (IS_ERR(c))
 		return -ENODEV;
 
-	if (!c->aim0.ptr)
-		aim_ptr = &c->aim0.ptr;
-	else if (!c->aim1.ptr)
-		aim_ptr = &c->aim1.ptr;
-	else
-		return -ENOSPC;
-
-	*aim_ptr = aim_obj->driver;
-	ret = aim_obj->driver->probe_channel(c->iface, c->channel_id,
-					     &c->cfg, &c->kobj, mdev_devnod);
-	if (ret) {
-		*aim_ptr = NULL;
+	ret = link_channel_to_aim(c, aim_obj->driver, aim_param);
+	if (ret)
 		return ret;
-	}
 
 	return len;
 }
@@ -1679,6 +1693,73 @@ int most_deregister_aim(struct most_aim *aim)
 }
 EXPORT_SYMBOL_GPL(most_deregister_aim);
 
+void most_register_config_set(struct most_config_set *cfg_set)
+{
+	mutex_lock(&config_probes_mt);
+	list_add(&cfg_set->list, &config_probes);
+	mutex_unlock(&config_probes_mt);
+}
+EXPORT_SYMBOL(most_register_config_set);
+
+void most_deregister_config_set(struct most_config_set *cfg_set)
+{
+	mutex_lock(&config_probes_mt);
+	list_del(&cfg_set->list);
+	mutex_unlock(&config_probes_mt);
+}
+EXPORT_SYMBOL(most_deregister_config_set);
+
+static int probe_aim(struct most_c_obj *c,
+		     const char *aim_name, const char *aim_param)
+{
+	struct most_aim_obj *aim_obj;
+	char buf[STRING_SIZE];
+
+	list_for_each_entry(aim_obj, &aim_list, list) {
+		if (!strcmp(aim_obj->driver->name, aim_name)) {
+			strlcpy(buf, aim_param ? aim_param : "", sizeof(buf));
+			return link_channel_to_aim(c, aim_obj->driver, buf);
+		}
+	}
+	return 0;
+}
+
+static bool probe_config_set(struct most_c_obj *c,
+			     const char *dev_name, const char *ch_name,
+			     const struct most_config_probe *p)
+{
+	int err;
+
+	for (; p->ch_name; p++) {
+		if ((p->dev_name && strcmp(dev_name, p->dev_name)) ||
+		    strcmp(ch_name, p->ch_name))
+			continue;
+
+		c->cfg = p->cfg;
+		if (p->aim_name) {
+			err = probe_aim(c, p->aim_name, p->aim_param);
+			if (err)
+				pr_err("failed to autolink %s to %s: %d\n",
+				       ch_name, p->aim_name, err);
+		}
+		return true;
+	}
+	return false;
+}
+
+static void find_configuration(struct most_c_obj *c, const char *dev_name,
+			       const char *ch_name)
+{
+	struct most_config_set *plist;
+
+	mutex_lock(&config_probes_mt);
+	list_for_each_entry(plist, &config_probes, list) {
+		if (probe_config_set(c, dev_name, ch_name, plist->probes))
+			break;
+	}
+	mutex_unlock(&config_probes_mt);
+}
+
 /**
  * most_register_interface - registers an interface with core
  * @iface: pointer to the instance of the interface description.
@@ -1777,6 +1858,7 @@ struct kobject *most_register_interface(struct most_interface *iface)
 		mutex_init(&c->start_mutex);
 		mutex_init(&c->nq_mutex);
 		list_add_tail(&c->list, &inst->channel_list);
+		find_configuration(c, iface->description, channel_name);
 	}
 	pr_info("registered new MOST device mdev%d (%s)\n",
 		inst->dev_id, iface->description);
@@ -1880,6 +1962,8 @@ static int __init most_init(void)
 	pr_info("init()\n");
 	INIT_LIST_HEAD(&instance_list);
 	INIT_LIST_HEAD(&aim_list);
+	INIT_LIST_HEAD(&config_probes);
+	mutex_init(&config_probes_mt);
 	ida_init(&mdev_id);
 
 	err = bus_register(&most_bus);
-- 
2.7.4