summaryrefslogtreecommitdiff
path: root/drivers/soundwire/sysfs_slave_dpn.c
diff options
context:
space:
mode:
authorPierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>2020-05-18 23:35:51 +0300
committerVinod Koul <vkoul@kernel.org>2020-05-20 14:52:41 +0300
commitbcac59029955ae57d3c660a0a1d6d4c7ae78fb49 (patch)
treed1ed29a40b4a6a59760fce57ce394e0154d7dc57 /drivers/soundwire/sysfs_slave_dpn.c
parentc5778ca49a19420c67dbeff0744a3b3b75ef4e1a (diff)
downloadlinux-bcac59029955ae57d3c660a0a1d6d4c7ae78fb49.tar.xz
soundwire: add Slave sysfs support
Expose MIPI DisCo Slave properties in sysfs. For Slave properties and Data Port 0, the attributes are managed with simple devm_ support. A Slave Device may have more than one Data Port (DPN), and each Data Port can be sink or source. The attributes are created dynamically using pre-canned macros, but still use devm_ with a name attribute group to avoid creating kobjects - as requested by GregKH. In the _show function, we use container_of() to retrieve port number and direction required to extract the information. Audio modes are not supported for now. Depending on the discussions the SoundWire Device Class, we may add it later as is or follow the new specification. Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Signed-off-by: Bard Liao <yung-chuan.liao@linux.intel.com> Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Link: https://lore.kernel.org/r/20200518203551.2053-4-yung-chuan.liao@linux.intel.com Signed-off-by: Vinod Koul <vkoul@kernel.org>
Diffstat (limited to 'drivers/soundwire/sysfs_slave_dpn.c')
-rw-r--r--drivers/soundwire/sysfs_slave_dpn.c300
1 files changed, 300 insertions, 0 deletions
diff --git a/drivers/soundwire/sysfs_slave_dpn.c b/drivers/soundwire/sysfs_slave_dpn.c
new file mode 100644
index 000000000000..c175a7d08815
--- /dev/null
+++ b/drivers/soundwire/sysfs_slave_dpn.c
@@ -0,0 +1,300 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2015-2020 Intel Corporation.
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+#include "sysfs_local.h"
+
+struct dpn_attribute {
+ struct device_attribute dev_attr;
+ int N;
+ int dir;
+ const char *format_string;
+};
+
+/*
+ * Since we can't use ARRAY_SIZE, hard-code number of dpN attributes.
+ * This needs to be updated when adding new attributes - an error will be
+ * flagged on a mismatch.
+ */
+#define SDW_DPN_ATTRIBUTES 15
+
+#define sdw_dpn_attribute_alloc(field) \
+static int field##_attribute_alloc(struct device *dev, \
+ struct attribute **res, \
+ int N, int dir, \
+ const char *format_string) \
+{ \
+ struct dpn_attribute *dpn_attr; \
+ \
+ dpn_attr = devm_kzalloc(dev, sizeof(*dpn_attr), GFP_KERNEL); \
+ if (!dpn_attr) \
+ return -ENOMEM; \
+ dpn_attr->N = N; \
+ dpn_attr->dir = dir; \
+ dpn_attr->format_string = format_string; \
+ dpn_attr->dev_attr.attr.name = __stringify(field); \
+ dpn_attr->dev_attr.attr.mode = 0444; \
+ dpn_attr->dev_attr.show = field##_show; \
+ \
+ *res = &dpn_attr->dev_attr.attr; \
+ \
+ return 0; \
+}
+
+#define sdw_dpn_attr(field) \
+ \
+static ssize_t field##_dpn_show(struct sdw_slave *slave, \
+ int N, \
+ int dir, \
+ const char *format_string, \
+ char *buf) \
+{ \
+ struct sdw_dpn_prop *dpn; \
+ unsigned long mask; \
+ int bit; \
+ int i; \
+ \
+ if (dir) { \
+ dpn = slave->prop.src_dpn_prop; \
+ mask = slave->prop.source_ports; \
+ } else { \
+ dpn = slave->prop.sink_dpn_prop; \
+ mask = slave->prop.sink_ports; \
+ } \
+ \
+ i = 0; \
+ for_each_set_bit(bit, &mask, 32) { \
+ if (bit == N) { \
+ return sprintf(buf, format_string, \
+ dpn[i].field); \
+ } \
+ i++; \
+ } \
+ return -EINVAL; \
+} \
+ \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_slave *slave = dev_to_sdw_dev(dev); \
+ struct dpn_attribute *dpn_attr = \
+ container_of(attr, struct dpn_attribute, dev_attr); \
+ \
+ return field##_dpn_show(slave, \
+ dpn_attr->N, dpn_attr->dir, \
+ dpn_attr->format_string, \
+ buf); \
+} \
+sdw_dpn_attribute_alloc(field)
+
+sdw_dpn_attr(imp_def_interrupts);
+sdw_dpn_attr(max_word);
+sdw_dpn_attr(min_word);
+sdw_dpn_attr(type);
+sdw_dpn_attr(max_grouping);
+sdw_dpn_attr(simple_ch_prep_sm);
+sdw_dpn_attr(ch_prep_timeout);
+sdw_dpn_attr(max_ch);
+sdw_dpn_attr(min_ch);
+sdw_dpn_attr(max_async_buffer);
+sdw_dpn_attr(block_pack_mode);
+sdw_dpn_attr(port_encoding);
+
+#define sdw_dpn_array_attr(field) \
+ \
+static ssize_t field##_dpn_show(struct sdw_slave *slave, \
+ int N, \
+ int dir, \
+ const char *format_string, \
+ char *buf) \
+{ \
+ struct sdw_dpn_prop *dpn; \
+ unsigned long mask; \
+ ssize_t size = 0; \
+ int bit; \
+ int i; \
+ int j; \
+ \
+ if (dir) { \
+ dpn = slave->prop.src_dpn_prop; \
+ mask = slave->prop.source_ports; \
+ } else { \
+ dpn = slave->prop.sink_dpn_prop; \
+ mask = slave->prop.sink_ports; \
+ } \
+ \
+ i = 0; \
+ for_each_set_bit(bit, &mask, 32) { \
+ if (bit == N) { \
+ for (j = 0; j < dpn[i].num_##field; j++) \
+ size += sprintf(buf + size, \
+ format_string, \
+ dpn[i].field[j]); \
+ size += sprintf(buf + size, "\n"); \
+ return size; \
+ } \
+ i++; \
+ } \
+ return -EINVAL; \
+} \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_slave *slave = dev_to_sdw_dev(dev); \
+ struct dpn_attribute *dpn_attr = \
+ container_of(attr, struct dpn_attribute, dev_attr); \
+ \
+ return field##_dpn_show(slave, \
+ dpn_attr->N, dpn_attr->dir, \
+ dpn_attr->format_string, \
+ buf); \
+} \
+sdw_dpn_attribute_alloc(field)
+
+sdw_dpn_array_attr(words);
+sdw_dpn_array_attr(ch_combinations);
+sdw_dpn_array_attr(channels);
+
+static int add_all_attributes(struct device *dev, int N, int dir)
+{
+ struct attribute **dpn_attrs;
+ struct attribute_group *dpn_group;
+ int i = 0;
+ int ret;
+
+ /* allocate attributes, last one is NULL */
+ dpn_attrs = devm_kcalloc(dev, SDW_DPN_ATTRIBUTES + 1,
+ sizeof(struct attribute *),
+ GFP_KERNEL);
+ if (!dpn_attrs)
+ return -ENOMEM;
+
+ ret = max_word_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = min_word_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = words_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = type_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = max_grouping_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = simple_ch_prep_sm_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = ch_prep_timeout_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = imp_def_interrupts_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "0x%x\n");
+ if (ret < 0)
+ return ret;
+
+ ret = min_ch_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = max_ch_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = channels_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = ch_combinations_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = max_async_buffer_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = block_pack_mode_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ ret = port_encoding_attribute_alloc(dev, &dpn_attrs[i++],
+ N, dir, "%d\n");
+ if (ret < 0)
+ return ret;
+
+ /* paranioa check for editing mistakes */
+ if (i != SDW_DPN_ATTRIBUTES) {
+ dev_err(dev, "mismatch in attributes, allocated %d got %d\n",
+ SDW_DPN_ATTRIBUTES, i);
+ return -EINVAL;
+ }
+
+ dpn_group = devm_kzalloc(dev, sizeof(*dpn_group), GFP_KERNEL);
+ if (!dpn_group)
+ return -ENOMEM;
+
+ dpn_group->attrs = dpn_attrs;
+ dpn_group->name = devm_kasprintf(dev, GFP_KERNEL, "dp%d_%s",
+ N, dir ? "src" : "sink");
+ if (!dpn_group->name)
+ return -ENOMEM;
+
+ ret = devm_device_add_group(dev, dpn_group);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave)
+{
+ unsigned long mask;
+ int ret;
+ int i;
+
+ mask = slave->prop.source_ports;
+ for_each_set_bit(i, &mask, 32) {
+ ret = add_all_attributes(&slave->dev, i, 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ mask = slave->prop.sink_ports;
+ for_each_set_bit(i, &mask, 32) {
+ ret = add_all_attributes(&slave->dev, i, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}