summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-05-22 09:25:25 +0200
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2020-05-22 09:25:25 +0200
commitddc0aef01a90ee8431f1a47f7b35e84d36ab8913 (patch)
treee0fd502efbaa74c955d271ea170d9e3268433874
parentc9cf27d9dee20193660b6582e4e6d6b315bff217 (diff)
parentfcbcf1f7b56855ffe1cec768cb1013c5a4c854c3 (diff)
downloadlinux-ddc0aef01a90ee8431f1a47f7b35e84d36ab8913.tar.gz
linux-ddc0aef01a90ee8431f1a47f7b35e84d36ab8913.tar.bz2
linux-ddc0aef01a90ee8431f1a47f7b35e84d36ab8913.zip
Merge tag 'soundwire-5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire into char-misc-next
Vinod writes: soundwire updates for v5.8-rc1 This contains sdw_master_device patches and other updates done by Intel folks. Details: - sdw_master_device to represent the master instances. - sysfs properties for sdw_master_device and sdw_slave. - Documentation update for TDM modes. - some code cleanup patches and odd updates. * tag 'soundwire-5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire: soundwire: intel: use a single module soundwire: fix spelling mistake soundwire: fix trailing line in sysfs_slave.c soundwire: add Slave sysfs support soundwire: master: add sysfs support soundwire: disco: s/ch/channels/ soundwire: master: add runtime pm support soundwire: bus_type: add sdw_master_device support soundwire: bus: add unique bus id soundwire: bus_type: introduce sdw_slave_type and sdw_master_type soundwire: bus: rename sdw_bus_master_add/delete, add arguments soundwire: intel: (cosmetic) remove multiple superfluous "else" statements soundwire: (cosmetic) remove multiple superfluous "else" statements soundwire: qcom: Use IRQF_ONESHOT soundwire: bus: reduce verbosity on enumeration soundwire: debugfs: clarify SDPX license with GPL-2.0-only soundwire: slave: don't init debugfs on device registration error Documentation: SoundWire: clarify TDM mode support soundwire: qcom: fix error handling in probe soundwire: intel: use asoc_rtd_to_cpu() / asoc_rtd_to_codec() macro for DAI pointer
-rw-r--r--Documentation/ABI/testing/sysfs-bus-soundwire-master23
-rw-r--r--Documentation/ABI/testing/sysfs-bus-soundwire-slave91
-rw-r--r--Documentation/driver-api/soundwire/stream.rst89
-rw-r--r--Documentation/driver-api/soundwire/summary.rst7
-rw-r--r--drivers/soundwire/Makefile8
-rw-r--r--drivers/soundwire/bus.c71
-rw-r--r--drivers/soundwire/bus.h4
-rw-r--r--drivers/soundwire/bus_type.c22
-rw-r--r--drivers/soundwire/cadence_master.c8
-rw-r--r--drivers/soundwire/debugfs.c2
-rw-r--r--drivers/soundwire/intel.c13
-rw-r--r--drivers/soundwire/intel_init.c4
-rw-r--r--drivers/soundwire/master.c172
-rw-r--r--drivers/soundwire/mipi_disco.c11
-rw-r--r--drivers/soundwire/qcom.c34
-rw-r--r--drivers/soundwire/slave.c10
-rw-r--r--drivers/soundwire/sysfs_local.h14
-rw-r--r--drivers/soundwire/sysfs_slave.c214
-rw-r--r--drivers/soundwire/sysfs_slave_dpn.c300
-rw-r--r--include/linux/soundwire/sdw.h32
-rw-r--r--include/linux/soundwire/sdw_type.h9
21 files changed, 1066 insertions, 72 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-soundwire-master b/Documentation/ABI/testing/sysfs-bus-soundwire-master
new file mode 100644
index 000000000000..46ef038d8722
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-soundwire-master
@@ -0,0 +1,23 @@
+What: /sys/bus/soundwire/devices/sdw-master-N/revision
+ /sys/bus/soundwire/devices/sdw-master-N/clk_stop_modes
+ /sys/bus/soundwire/devices/sdw-master-N/clk_freq
+ /sys/bus/soundwire/devices/sdw-master-N/clk_gears
+ /sys/bus/soundwire/devices/sdw-master-N/default_col
+ /sys/bus/soundwire/devices/sdw-master-N/default_frame_rate
+ /sys/bus/soundwire/devices/sdw-master-N/default_row
+ /sys/bus/soundwire/devices/sdw-master-N/dynamic_shape
+ /sys/bus/soundwire/devices/sdw-master-N/err_threshold
+ /sys/bus/soundwire/devices/sdw-master-N/max_clk_freq
+
+Date: April 2020
+
+Contact: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
+ Bard Liao <yung-chuan.liao@linux.intel.com>
+ Vinod Koul <vkoul@kernel.org>
+
+Description: SoundWire Master-N DisCo properties.
+ These properties are defined by MIPI DisCo Specification
+ for SoundWire. They define various properties of the Master
+ and are used by the bus to configure the Master. clk_stop_modes
+ is a bitmask for simplifications and combines the
+ clock-stop-mode0 and clock-stop-mode1 properties.
diff --git a/Documentation/ABI/testing/sysfs-bus-soundwire-slave b/Documentation/ABI/testing/sysfs-bus-soundwire-slave
new file mode 100644
index 000000000000..db4c9511d1aa
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-soundwire-slave
@@ -0,0 +1,91 @@
+What: /sys/bus/soundwire/devices/sdw:.../dev-properties/mipi_revision
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/wake_capable
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/test_mode_capable
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/clk_stop_mode1
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/simple_clk_stop_capable
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/clk_stop_timeout
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/ch_prep_timeout
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/reset_behave
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/high_PHY_capable
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/paging_support
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/bank_delay_support
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/p15_behave
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/master_count
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/source_ports
+ /sys/bus/soundwire/devices/sdw:.../dev-properties/sink_ports
+
+Date: May 2020
+
+Contact: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
+ Bard Liao <yung-chuan.liao@linux.intel.com>
+ Vinod Koul <vkoul@kernel.org>
+
+Description: SoundWire Slave DisCo properties.
+ These properties are defined by MIPI DisCo Specification
+ for SoundWire. They define various properties of the
+ SoundWire Slave and are used by the bus to configure
+ the Slave
+
+
+What: /sys/bus/soundwire/devices/sdw:.../dp0/max_word
+ /sys/bus/soundwire/devices/sdw:.../dp0/min_word
+ /sys/bus/soundwire/devices/sdw:.../dp0/words
+ /sys/bus/soundwire/devices/sdw:.../dp0/BRA_flow_controlled
+ /sys/bus/soundwire/devices/sdw:.../dp0/simple_ch_prep_sm
+ /sys/bus/soundwire/devices/sdw:.../dp0/imp_def_interrupts
+
+Date: May 2020
+
+Contact: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
+ Bard Liao <yung-chuan.liao@linux.intel.com>
+ Vinod Koul <vkoul@kernel.org>
+
+Description: SoundWire Slave Data Port-0 DisCo properties.
+ These properties are defined by MIPI DisCo Specification
+ for the SoundWire. They define various properties of the
+ Data port 0 are used by the bus to configure the Data Port 0.
+
+
+What: /sys/bus/soundwire/devices/sdw:.../dpN_src/max_word
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/min_word
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/words
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/type
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/max_grouping
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/simple_ch_prep_sm
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/ch_prep_timeout
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/imp_def_interrupts
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/min_ch
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/max_ch
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/channels
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/ch_combinations
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/max_async_buffer
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/block_pack_mode
+ /sys/bus/soundwire/devices/sdw:.../dpN_src/port_encoding
+
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/max_word
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/min_word
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/words
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/type
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/max_grouping
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/simple_ch_prep_sm
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/ch_prep_timeout
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/imp_def_interrupts
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/min_ch
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/max_ch
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/channels
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/ch_combinations
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/max_async_buffer
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/block_pack_mode
+ /sys/bus/soundwire/devices/sdw:.../dpN_sink/port_encoding
+
+Date: May 2020
+
+Contact: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
+ Bard Liao <yung-chuan.liao@linux.intel.com>
+ Vinod Koul <vkoul@kernel.org>
+
+Description: SoundWire Slave Data Source/Sink Port-N DisCo properties.
+ These properties are defined by MIPI DisCo Specification
+ for SoundWire. They define various properties of the
+ Source/Sink Data port N and are used by the bus to configure
+ the Data Port N.
diff --git a/Documentation/driver-api/soundwire/stream.rst b/Documentation/driver-api/soundwire/stream.rst
index 8bceece51554..1b386076402c 100644
--- a/Documentation/driver-api/soundwire/stream.rst
+++ b/Documentation/driver-api/soundwire/stream.rst
@@ -75,8 +75,33 @@ Slaves are using single port. ::
| (Data) |
+---------------+
+Example 4: Stereo Stream with L and R channels is rendered by
+Master. Both of the L and R channels are received by two different
+Slaves. Master and both Slaves are using single port handling
+L+R. Each Slave device processes the L + R data locally, typically
+based on static configuration or dynamic orientation, and may drive
+one or more speakers. ::
-Example 4: Stereo Stream with L and R channel is rendered by two different
+ +---------------+ Clock Signal +---------------+
+ | Master +---------+------------------------+ Slave |
+ | Interface | | | Interface |
+ | | | | 1 |
+ | | | Data Signal | |
+ | L + R +---+------------------------------+ L + R |
+ | (Data) | | | Data Direction | (Data) |
+ +---------------+ | | +-------------> +---------------+
+ | |
+ | |
+ | | +---------------+
+ | +----------------------> | Slave |
+ | | Interface |
+ | | 2 |
+ | | |
+ +----------------------------> | L + R |
+ | (Data) |
+ +---------------+
+
+Example 5: Stereo Stream with L and R channel is rendered by two different
Ports of the Master and is received by only single Port of the Slave
interface. ::
@@ -101,7 +126,7 @@ interface. ::
+--------------------+ | |
+----------------+
-Example 5: Stereo Stream with L and R channel is rendered by 2 Masters, each
+Example 6: Stereo Stream with L and R channel is rendered by 2 Masters, each
rendering one channel, and is received by two different Slaves, each
receiving one channel. Both Masters and both Slaves are using single port. ::
@@ -123,12 +148,70 @@ receiving one channel. Both Masters and both Slaves are using single port. ::
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
-Note: In multi-link cases like above, to lock, one would acquire a global
+Example 7: Stereo Stream with L and R channel is rendered by 2
+Masters, each rendering both channels. Each Slave receives L + R. This
+is the same application as Example 4 but with Slaves placed on
+separate links. ::
+
+ +---------------+ Clock Signal +---------------+
+ | Master +----------------------------------+ Slave |
+ | Interface | | Interface |
+ | 1 | | 1 |
+ | | Data Signal | |
+ | L + R +----------------------------------+ L + R |
+ | (Data) | Data Direction | (Data) |
+ +---------------+ +-----------------------> +---------------+
+
+ +---------------+ Clock Signal +---------------+
+ | Master +----------------------------------+ Slave |
+ | Interface | | Interface |
+ | 2 | | 2 |
+ | | Data Signal | |
+ | L + R +----------------------------------+ L + R |
+ | (Data) | Data Direction | (Data) |
+ +---------------+ +-----------------------> +---------------+
+
+Example 8: 4-channel Stream is rendered by 2 Masters, each rendering a
+2 channels. Each Slave receives 2 channels. ::
+
+ +---------------+ Clock Signal +---------------+
+ | Master +----------------------------------+ Slave |
+ | Interface | | Interface |
+ | 1 | | 1 |
+ | | Data Signal | |
+ | L1 + R1 +----------------------------------+ L1 + R1 |
+ | (Data) | Data Direction | (Data) |
+ +---------------+ +-----------------------> +---------------+
+
+ +---------------+ Clock Signal +---------------+
+ | Master +----------------------------------+ Slave |
+ | Interface | | Interface |
+ | 2 | | 2 |
+ | | Data Signal | |
+ | L2 + R2 +----------------------------------+ L2 + R2 |
+ | (Data) | Data Direction | (Data) |
+ +---------------+ +-----------------------> +---------------+
+
+Note1: In multi-link cases like above, to lock, one would acquire a global
lock and then go on locking bus instances. But, in this case the caller
framework(ASoC DPCM) guarantees that stream operations on a card are
always serialized. So, there is no race condition and hence no need for
global lock.
+Note2: A Slave device may be configured to receive all channels
+transmitted on a link for a given Stream (Example 4) or just a subset
+of the data (Example 3). The configuration of the Slave device is not
+handled by a SoundWire subsystem API, but instead by the
+snd_soc_dai_set_tdm_slot() API. The platform or machine driver will
+typically configure which of the slots are used. For Example 4, the
+same slots would be used by all Devices, while for Example 3 the Slave
+Device1 would use e.g. Slot 0 and Slave device2 slot 1.
+
+Note3: Multiple Sink ports can extract the same information for the
+same bitSlots in the SoundWire frame, however multiple Source ports
+shall be configured with different bitSlot configurations. This is the
+same limitation as with I2S/PCM TDM usages.
+
SoundWire Stream Management flow
================================
diff --git a/Documentation/driver-api/soundwire/summary.rst b/Documentation/driver-api/soundwire/summary.rst
index 8193125a2bfb..01dcb954f6d7 100644
--- a/Documentation/driver-api/soundwire/summary.rst
+++ b/Documentation/driver-api/soundwire/summary.rst
@@ -101,10 +101,11 @@ Following is the Bus API to register the SoundWire Bus:
.. code-block:: c
- int sdw_add_bus_master(struct sdw_bus *bus)
+ int sdw_bus_master_add(struct sdw_bus *bus,
+ struct device *parent,
+ struct fwnode_handle)
{
- if (!bus->dev)
- return -ENODEV;
+ sdw_master_device_add(bus, parent, fwnode);
mutex_init(&bus->lock);
INIT_LIST_HEAD(&bus->slaves);
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile
index e2cdff990e9f..b5871612613b 100644
--- a/drivers/soundwire/Makefile
+++ b/drivers/soundwire/Makefile
@@ -4,7 +4,8 @@
#
#Bus Objs
-soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o stream.o
+soundwire-bus-objs := bus_type.o bus.o master.o slave.o mipi_disco.o stream.o \
+ sysfs_slave.o sysfs_slave_dpn.o
obj-$(CONFIG_SOUNDWIRE) += soundwire-bus.o
ifdef CONFIG_DEBUG_FS
@@ -16,12 +17,9 @@ soundwire-cadence-objs := cadence_master.o
obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o
#Intel driver
-soundwire-intel-objs := intel.o
+soundwire-intel-objs := intel.o intel_init.o
obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o
-soundwire-intel-init-objs := intel_init.o
-obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel-init.o
-
#Qualcomm driver
soundwire-qcom-objs := qcom.o
obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c
index 488c3c9e4947..24ba77226376 100644
--- a/drivers/soundwire/bus.c
+++ b/drivers/soundwire/bus.c
@@ -8,24 +8,54 @@
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include "bus.h"
+#include "sysfs_local.h"
+
+static DEFINE_IDA(sdw_ida);
+
+static int sdw_get_id(struct sdw_bus *bus)
+{
+ int rc = ida_alloc(&sdw_ida, GFP_KERNEL);
+
+ if (rc < 0)
+ return rc;
+
+ bus->id = rc;
+ return 0;
+}
/**
- * sdw_add_bus_master() - add a bus Master instance
+ * sdw_bus_master_add() - add a bus Master instance
* @bus: bus instance
+ * @parent: parent device
+ * @fwnode: firmware node handle
*
* Initializes the bus instance, read properties and create child
* devices.
*/
-int sdw_add_bus_master(struct sdw_bus *bus)
+int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent,
+ struct fwnode_handle *fwnode)
{
struct sdw_master_prop *prop = NULL;
int ret;
- if (!bus->dev) {
- pr_err("SoundWire bus has no device\n");
+ if (!parent) {
+ pr_err("SoundWire parent device is not set\n");
return -ENODEV;
}
+ ret = sdw_get_id(bus);
+ if (ret) {
+ dev_err(parent, "Failed to get bus id\n");
+ return ret;
+ }
+
+ ret = sdw_master_device_add(bus, parent, fwnode);
+ if (ret) {
+ dev_err(parent, "Failed to add master device at link %d\n",
+ bus->link_id);
+ return ret;
+ }
+
if (!bus->ops) {
dev_err(bus->dev, "SoundWire Bus ops are not set\n");
return -EINVAL;
@@ -107,7 +137,7 @@ int sdw_add_bus_master(struct sdw_bus *bus)
return 0;
}
-EXPORT_SYMBOL(sdw_add_bus_master);
+EXPORT_SYMBOL(sdw_bus_master_add);
static int sdw_delete_slave(struct device *dev, void *data)
{
@@ -131,18 +161,20 @@ static int sdw_delete_slave(struct device *dev, void *data)
}
/**
- * sdw_delete_bus_master() - delete the bus master instance
+ * sdw_bus_master_delete() - delete the bus master instance
* @bus: bus to be deleted
*
* Remove the instance, delete the child devices.
*/
-void sdw_delete_bus_master(struct sdw_bus *bus)
+void sdw_bus_master_delete(struct sdw_bus *bus)
{
device_for_each_child(bus->dev, NULL, sdw_delete_slave);
+ sdw_master_device_del(bus);
sdw_bus_debugfs_exit(bus);
+ ida_free(&sdw_ida, bus->id);
}
-EXPORT_SYMBOL(sdw_delete_bus_master);
+EXPORT_SYMBOL(sdw_bus_master_delete);
/*
* SDW IO Calls
@@ -284,9 +316,10 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
msg->flags = flags;
msg->buf = buf;
- if (addr < SDW_REG_NO_PAGE) { /* no paging area */
+ if (addr < SDW_REG_NO_PAGE) /* no paging area */
return 0;
- } else if (addr >= SDW_REG_MAX) { /* illegal addr */
+
+ if (addr >= SDW_REG_MAX) { /* illegal addr */
pr_err("SDW: Invalid address %x passed\n", addr);
return -EINVAL;
}
@@ -306,7 +339,9 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
if (!slave) {
pr_err("SDW: No slave for paging addr\n");
return -EINVAL;
- } else if (!slave->prop.paging_support) {
+ }
+
+ if (!slave->prop.paging_support) {
dev_err(&slave->dev,
"address %x needs paging but no support\n", addr);
return -EINVAL;
@@ -375,8 +410,8 @@ sdw_bread_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr)
ret = sdw_transfer(bus, &msg);
if (ret < 0)
return ret;
- else
- return buf;
+
+ return buf;
}
static int
@@ -471,8 +506,8 @@ int sdw_read(struct sdw_slave *slave, u32 addr)
ret = sdw_nread(slave, addr, 1, &buf);
if (ret < 0)
return ret;
- else
- return buf;
+
+ return buf;
}
EXPORT_SYMBOL(sdw_read);
@@ -563,9 +598,9 @@ static int sdw_assign_device_num(struct sdw_slave *slave)
}
if (!new_device)
- dev_info(slave->bus->dev,
- "Slave already registered, reusing dev_num:%d\n",
- slave->dev_num);
+ dev_dbg(slave->bus->dev,
+ "Slave already registered, reusing dev_num:%d\n",
+ slave->dev_num);
/* Clear the slave->dev_num to transfer message on device 0 */
dev_num = slave->dev_num;
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h
index 204204a26db8..82484f741168 100644
--- a/drivers/soundwire/bus.h
+++ b/drivers/soundwire/bus.h
@@ -19,6 +19,9 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus)
int sdw_of_find_slaves(struct sdw_bus *bus);
void sdw_extract_slave_id(struct sdw_bus *bus,
u64 addr, struct sdw_slave_id *id);
+int sdw_master_device_add(struct sdw_bus *bus, struct device *parent,
+ struct fwnode_handle *fwnode);
+int sdw_master_device_del(struct sdw_bus *bus);
#ifdef CONFIG_DEBUG_FS
void sdw_bus_debugfs_init(struct sdw_bus *bus);
@@ -172,5 +175,6 @@ sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
#define SDW_UNATTACH_REQUEST_MASTER_RESET BIT(0)
void sdw_clear_slave_status(struct sdw_bus *bus, u32 request);
+int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size);
#endif /* __SDW_BUS_H */
diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c
index 17f096dd6806..de9a671802b8 100644
--- a/drivers/soundwire/bus_type.c
+++ b/drivers/soundwire/bus_type.c
@@ -7,6 +7,7 @@
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
+#include "sysfs_local.h"
/**
* sdw_get_device_id - find the matching SoundWire device id
@@ -33,10 +34,17 @@ sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv)
static int sdw_bus_match(struct device *dev, struct device_driver *ddrv)
{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- struct sdw_driver *drv = drv_to_sdw_driver(ddrv);
+ struct sdw_slave *slave;
+ struct sdw_driver *drv;
+ int ret = 0;
+
+ if (is_sdw_slave(dev)) {
+ slave = dev_to_sdw_dev(dev);
+ drv = drv_to_sdw_driver(ddrv);
- return !!sdw_get_device_id(slave, drv);
+ ret = !!sdw_get_device_id(slave, drv);
+ }
+ return ret;
}
int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size)
@@ -47,7 +55,7 @@ int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size)
slave->id.mfg_id, slave->id.part_id);
}
-static int sdw_uevent(struct device *dev, struct kobj_uevent_env *env)
+int sdw_slave_uevent(struct device *dev, struct kobj_uevent_env *env)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
char modalias[32];
@@ -63,7 +71,6 @@ static int sdw_uevent(struct device *dev, struct kobj_uevent_env *env)
struct bus_type sdw_bus_type = {
.name = "soundwire",
.match = sdw_bus_match,
- .uevent = sdw_uevent,
};
EXPORT_SYMBOL_GPL(sdw_bus_type);
@@ -98,6 +105,11 @@ static int sdw_drv_probe(struct device *dev)
if (slave->ops && slave->ops->read_prop)
slave->ops->read_prop(slave);
+ /* init the sysfs as we have properties now */
+ ret = sdw_slave_sysfs_init(slave);
+ if (ret < 0)
+ dev_warn(dev, "Slave sysfs init failed:%d\n", ret);
+
/*
* Check for valid clk_stop_timeout, use DisCo worst case value of
* 300ms
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c
index ecd357d1c63d..9ea87538b9ef 100644
--- a/drivers/soundwire/cadence_master.c
+++ b/drivers/soundwire/cadence_master.c
@@ -407,7 +407,9 @@ cdns_fill_msg_resp(struct sdw_cdns *cdns,
if (nack) {
dev_err_ratelimited(cdns->dev, "Msg NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- } else if (no_ack) {
+ }
+
+ if (no_ack) {
dev_dbg_ratelimited(cdns->dev, "Msg ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
}
@@ -520,7 +522,9 @@ cdns_program_scp_addr(struct sdw_cdns *cdns, struct sdw_msg *msg)
dev_err_ratelimited(cdns->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- } else if (no_ack) {
+ }
+
+ if (no_ack) {
dev_dbg_ratelimited(cdns->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c
index fb1140e82b86..b6cad0d59b7b 100644
--- a/drivers/soundwire/debugfs.c
+++ b/drivers/soundwire/debugfs.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2017-2019 Intel Corporation.
#include <linux/device.h>
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c
index 3c83e76c6bf9..4cfdd074e310 100644
--- a/drivers/soundwire/intel.c
+++ b/drivers/soundwire/intel.c
@@ -669,11 +669,11 @@ static int sdw_stream_setup(struct snd_pcm_substream *substream,
/* Set stream pointer on all CODEC DAIs */
for (i = 0; i < rtd->num_codecs; i++) {
- ret = snd_soc_dai_set_sdw_stream(rtd->codec_dais[i], sdw_stream,
+ ret = snd_soc_dai_set_sdw_stream(asoc_rtd_to_codec(rtd, i), sdw_stream,
substream->stream);
if (ret < 0) {
dev_err(dai->dev, "failed to set stream pointer on codec dai %s",
- rtd->codec_dais[i]->name);
+ asoc_rtd_to_codec(rtd, i)->name);
goto release_stream;
}
}
@@ -1099,7 +1099,6 @@ static int intel_probe(struct platform_device *pdev)
sdw->cdns.registers = sdw->link_res->registers;
sdw->cdns.instance = sdw->instance;
sdw->cdns.msg_count = 0;
- sdw->cdns.bus.dev = &pdev->dev;
sdw->cdns.bus.link_id = pdev->id;
sdw_cdns_probe(&sdw->cdns);
@@ -1110,9 +1109,9 @@ static int intel_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, sdw);
- ret = sdw_add_bus_master(&sdw->cdns.bus);
+ ret = sdw_bus_master_add(&sdw->cdns.bus, &pdev->dev, pdev->dev.fwnode);
if (ret) {
- dev_err(&pdev->dev, "sdw_add_bus_master fail: %d\n", ret);
+ dev_err(&pdev->dev, "sdw_bus_master_add fail: %d\n", ret);
return ret;
}
@@ -1173,7 +1172,7 @@ err_interrupt:
sdw_cdns_enable_interrupt(&sdw->cdns, false);
free_irq(sdw->link_res->irq, sdw);
err_init:
- sdw_delete_bus_master(&sdw->cdns.bus);
+ sdw_bus_master_delete(&sdw->cdns.bus);
return ret;
}
@@ -1189,7 +1188,7 @@ static int intel_remove(struct platform_device *pdev)
free_irq(sdw->link_res->irq, sdw);
snd_soc_unregister_component(sdw->cdns.dev);
}
- sdw_delete_bus_master(&sdw->cdns.bus);
+ sdw_bus_master_delete(&sdw->cdns.bus);
return 0;
}
diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c
index 4b769409f6f8..d5d42795a48f 100644
--- a/drivers/soundwire/intel_init.c
+++ b/drivers/soundwire/intel_init.c
@@ -86,7 +86,9 @@ static struct sdw_intel_ctx
dev_err(&adev->dev, "Link count %d exceeds max %d\n",
count, SDW_MAX_LINKS);
return NULL;
- } else if (!count) {
+ }
+
+ if (!count) {
dev_warn(&adev->dev, "No SoundWire links detected\n");
return NULL;
}
diff --git a/drivers/soundwire/master.c b/drivers/soundwire/master.c
new file mode 100644
index 000000000000..5f0b2189defe
--- /dev/null
+++ b/drivers/soundwire/master.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright(c) 2019-2020 Intel Corporation.
+
+#include <linux/device.h>
+#include <linux/acpi.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "bus.h"
+
+/*
+ * The sysfs for properties reflects the MIPI description as given
+ * in the MIPI DisCo spec
+ *
+ * Base file is:
+ * sdw-master-N
+ * |---- revision
+ * |---- clk_stop_modes
+ * |---- max_clk_freq
+ * |---- clk_freq
+ * |---- clk_gears
+ * |---- default_row
+ * |---- default_col
+ * |---- dynamic_shape
+ * |---- err_threshold
+ */
+
+#define sdw_master_attr(field, format_string) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_master_device *md = dev_to_sdw_master_device(dev); \
+ return sprintf(buf, format_string, md->bus->prop.field); \
+} \
+static DEVICE_ATTR_RO(field)
+
+sdw_master_attr(revision, "0x%x\n");
+sdw_master_attr(clk_stop_modes, "0x%x\n");
+sdw_master_attr(max_clk_freq, "%d\n");
+sdw_master_attr(default_row, "%d\n");
+sdw_master_attr(default_col, "%d\n");
+sdw_master_attr(default_frame_rate, "%d\n");
+sdw_master_attr(dynamic_frame, "%d\n");
+sdw_master_attr(err_threshold, "%d\n");
+
+static ssize_t clock_frequencies_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_master_device *md = dev_to_sdw_master_device(dev);
+ ssize_t size = 0;
+ int i;
+
+ for (i = 0; i < md->bus->prop.num_clk_freq; i++)
+ size += sprintf(buf + size, "%8d ",
+ md->bus->prop.clk_freq[i]);
+ size += sprintf(buf + size, "\n");
+
+ return size;
+}
+static DEVICE_ATTR_RO(clock_frequencies);
+
+static ssize_t clock_gears_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_master_device *md = dev_to_sdw_master_device(dev);
+ ssize_t size = 0;
+ int i;
+
+ for (i = 0; i < md->bus->prop.num_clk_gears; i++)
+ size += sprintf(buf + size, "%8d ",
+ md->bus->prop.clk_gears[i]);
+ size += sprintf(buf + size, "\n");
+
+ return size;
+}
+static DEVICE_ATTR_RO(clock_gears);
+
+static struct attribute *master_node_attrs[] = {
+ &dev_attr_revision.attr,
+ &dev_attr_clk_stop_modes.attr,
+ &dev_attr_max_clk_freq.attr,
+ &dev_attr_default_row.attr,
+ &dev_attr_default_col.attr,
+ &dev_attr_default_frame_rate.attr,
+ &dev_attr_dynamic_frame.attr,
+ &dev_attr_err_threshold.attr,
+ &dev_attr_clock_frequencies.attr,
+ &dev_attr_clock_gears.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(master_node);
+
+static void sdw_master_device_release(struct device *dev)
+{
+ struct sdw_master_device *md = dev_to_sdw_master_device(dev);
+
+ kfree(md);
+}
+
+static const struct dev_pm_ops master_dev_pm = {
+ SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend,
+ pm_generic_runtime_resume, NULL)
+};
+
+struct device_type sdw_master_type = {
+ .name = "soundwire_master",
+ .release = sdw_master_device_release,
+ .pm = &master_dev_pm,
+};
+
+/**
+ * sdw_master_device_add() - create a Linux Master Device representation.
+ * @bus: SDW bus instance
+ * @parent: parent device
+ * @fwnode: firmware node handle
+ */
+int sdw_master_device_add(struct sdw_bus *bus, struct device *parent,
+ struct fwnode_handle *fwnode)
+{
+ struct sdw_master_device *md;
+ int ret;
+
+ if (!parent)
+ return -EINVAL;
+
+ md = kzalloc(sizeof(*md), GFP_KERNEL);
+ if (!md)
+ return -ENOMEM;
+
+ md->dev.bus = &sdw_bus_type;
+ md->dev.type = &sdw_master_type;
+ md->dev.parent = parent;
+ md->dev.groups = master_node_groups;
+ md->dev.of_node = parent->of_node;
+ md->dev.fwnode = fwnode;
+ md->dev.dma_mask = parent->dma_mask;
+
+ dev_set_name(&md->dev, "sdw-master-%d", bus->id);
+
+ ret = device_register(&md->dev);
+ if (ret) {
+ dev_err(parent, "Failed to add master: ret %d\n", ret);
+ /*
+ * On err, don't free but drop ref as this will be freed
+ * when release method is invoked.
+ */
+ put_device(&md->dev);
+ goto device_register_err;
+ }
+
+ /* add shortcuts to improve code readability/compactness */
+ md->bus = bus;
+ bus->dev = &md->dev;
+ bus->md = md;
+
+device_register_err:
+ return ret;
+}
+
+/**
+ * sdw_master_device_del() - delete a Linux Master Device representation.
+ * @bus: bus handle
+ *
+ * This function is the dual of sdw_master_device_add()
+ */
+int sdw_master_device_del(struct sdw_bus *bus)
+{
+ device_unregister(bus->dev);
+
+ return 0;
+}
diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c
index 844e6b22974f..4ae62b452b8c 100644
--- a/drivers/soundwire/mipi_disco.c
+++ b/drivers/soundwire/mipi_disco.c
@@ -231,16 +231,17 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave,
nval = fwnode_property_count_u32(node, "mipi-sdw-channel-number-list");
if (nval > 0) {
- dpn[i].num_ch = nval;
- dpn[i].ch = devm_kcalloc(&slave->dev, dpn[i].num_ch,
- sizeof(*dpn[i].ch),
+ dpn[i].num_channels = nval;
+ dpn[i].channels = devm_kcalloc(&slave->dev,
+ dpn[i].num_channels,
+ sizeof(*dpn[i].channels),
GFP_KERNEL);
- if (!dpn[i].ch)
+ if (!dpn[i].channels)
return -ENOMEM;
fwnode_property_read_u32_array(node,
"mipi-sdw-channel-number-list",
- dpn[i].ch, dpn[i].num_ch);
+ dpn[i].channels, dpn[i].num_channels);
}
nval = fwnode_property_count_u32(node, "mipi-sdw-channel-combination-list");
diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c
index d6c9ad231873..a1c2a44a3b4d 100644
--- a/drivers/soundwire/qcom.c
+++ b/drivers/soundwire/qcom.c
@@ -765,12 +765,16 @@ static int qcom_swrm_probe(struct platform_device *pdev)
}
ctrl->irq = of_irq_get(dev->of_node, 0);
- if (ctrl->irq < 0)
- return ctrl->irq;
+ if (ctrl->irq < 0) {
+ ret = ctrl->irq;
+ goto err_init;
+ }
ctrl->hclk = devm_clk_get(dev, "iface");
- if (IS_ERR(ctrl->hclk))
- return PTR_ERR(ctrl->hclk);
+ if (IS_ERR(ctrl->hclk)) {
+ ret = PTR_ERR(ctrl->hclk);
+ goto err_init;
+ }
clk_prepare_enable(ctrl->hclk);
@@ -780,14 +784,13 @@ static int qcom_swrm_probe(struct platform_device *pdev)
mutex_init(&ctrl->port_lock);
INIT_WORK(&ctrl->slave_work, qcom_swrm_slave_wq);
- ctrl->bus.dev = dev;
ctrl->bus.ops = &qcom_swrm_ops;
ctrl->bus.port_ops = &qcom_swrm_port_ops;
ctrl->bus.compute_params = &qcom_swrm_compute_params;
ret = qcom_swrm_get_port_config(ctrl);
if (ret)
- return ret;
+ goto err_clk;
params = &ctrl->bus.params;
params->max_dr_freq = DEFAULT_CLK_FREQ;
@@ -810,32 +813,37 @@ static int qcom_swrm_probe(struct platform_device *pdev)
ret = devm_request_threaded_irq(dev, ctrl->irq, NULL,
qcom_swrm_irq_handler,
- IRQF_TRIGGER_RISING,
+ IRQF_TRIGGER_RISING |
+ IRQF_ONESHOT,
"soundwire", ctrl);
if (ret) {
dev_err(dev, "Failed to request soundwire irq\n");
- goto err;
+ goto err_clk;
}
- ret = sdw_add_bus_master(&ctrl->bus);
+ ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode);
if (ret) {
dev_err(dev, "Failed to register Soundwire controller (%d)\n",
ret);
- goto err;
+ goto err_clk;
}
qcom_swrm_init(ctrl);
ret = qcom_swrm_register_dais(ctrl);
if (ret)
- goto err;
+ goto err_master_add;
dev_info(dev, "Qualcomm Soundwire controller v%x.%x.%x Registered\n",
(ctrl->version >> 24) & 0xff, (ctrl->version >> 16) & 0xff,
ctrl->version & 0xffff);
return 0;
-err:
+
+err_master_add:
+ sdw_bus_master_delete(&ctrl->bus);
+err_clk:
clk_disable_unprepare(ctrl->hclk);
+err_init:
return ret;
}
@@ -843,7 +851,7 @@ static int qcom_swrm_remove(struct platform_device *pdev)
{
struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(&pdev->dev);
- sdw_delete_bus_master(&ctrl->bus);
+ sdw_bus_master_delete(&ctrl->bus);
clk_disable_unprepare(ctrl->hclk);
return 0;
diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c
index aace57fae7f8..0839445ee07b 100644
--- a/drivers/soundwire/slave.c
+++ b/drivers/soundwire/slave.c
@@ -14,6 +14,12 @@ static void sdw_slave_release(struct device *dev)
kfree(slave);
}
+struct device_type sdw_slave_type = {
+ .name = "sdw_slave",
+ .release = sdw_slave_release,
+ .uevent = sdw_slave_uevent,
+};
+
static int sdw_slave_add(struct sdw_bus *bus,
struct sdw_slave_id *id, struct fwnode_handle *fwnode)
{
@@ -41,9 +47,9 @@ static int sdw_slave_add(struct sdw_bus *bus,
id->class_id, id->unique_id);
}
- slave->dev.release = sdw_slave_release;
slave->dev.bus = &sdw_bus_type;
slave->dev.of_node = of_node_get(to_of_node(fwnode));
+ slave->dev.type = &sdw_slave_type;
slave->bus = bus;
slave->status = SDW_SLAVE_UNATTACHED;
init_completion(&slave->enumeration_complete);
@@ -68,6 +74,8 @@ static int sdw_slave_add(struct sdw_bus *bus,
list_del(&slave->node);
mutex_unlock(&bus->bus_lock);
put_device(&slave->dev);
+
+ return ret;
}
sdw_slave_debugfs_init(slave);
diff --git a/drivers/soundwire/sysfs_local.h b/drivers/soundwire/sysfs_local.h
new file mode 100644
index 000000000000..ff60adee3c41
--- /dev/null
+++ b/drivers/soundwire/sysfs_local.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright(c) 2015-2020 Intel Corporation. */
+
+#ifndef __SDW_SYSFS_LOCAL_H
+#define __SDW_SYSFS_LOCAL_H
+
+/*
+ * SDW sysfs APIs -
+ */
+
+int sdw_slave_sysfs_init(struct sdw_slave *slave);
+int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave);
+
+#endif /* __SDW_SYSFS_LOCAL_H */
diff --git a/drivers/soundwire/sysfs_slave.c b/drivers/soundwire/sysfs_slave.c
new file mode 100644
index 000000000000..f510071b0add
--- /dev/null
+++ b/drivers/soundwire/sysfs_slave.c
@@ -0,0 +1,214 @@
+// 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"
+
+/*
+ * Slave sysfs
+ */
+
+/*
+ * The sysfs for Slave reflects the MIPI description as given
+ * in the MIPI DisCo spec
+ *
+ * Base file is device
+ * |---- modalias
+ * |---- dev-properties
+ * |---- mipi_revision
+ * |---- wake_capable
+ * |---- test_mode_capable
+ * |---- clk_stop_mode1
+ * |---- simple_clk_stop_capable
+ * |---- clk_stop_timeout
+ * |---- ch_prep_timeout
+ * |---- reset_behave
+ * |---- high_PHY_capable
+ * |---- paging_support
+ * |---- bank_delay_support
+ * |---- p15_behave
+ * |---- master_count
+ * |---- source_ports
+ * |---- sink_ports
+ * |---- dp0
+ * |---- max_word
+ * |---- min_word
+ * |---- words
+ * |---- BRA_flow_controlled
+ * |---- simple_ch_prep_sm
+ * |---- imp_def_interrupts
+ * |---- dpN_<sink/src>
+ * |---- max_word
+ * |---- min_word
+ * |---- words
+ * |---- type
+ * |---- max_grouping
+ * |---- simple_ch_prep_sm
+ * |---- ch_prep_timeout
+ * |---- imp_def_interrupts
+ * |---- min_ch
+ * |---- max_ch
+ * |---- channels
+ * |---- ch_combinations
+ * |---- max_async_buffer
+ * |---- block_pack_mode
+ * |---- port_encoding
+ *
+ */
+
+#define sdw_slave_attr(field, format_string) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_slave *slave = dev_to_sdw_dev(dev); \
+ return sprintf(buf, format_string, slave->prop.field); \
+} \
+static DEVICE_ATTR_RO(field)
+
+sdw_slave_attr(mipi_revision, "0x%x\n");
+sdw_slave_attr(wake_capable, "%d\n");
+sdw_slave_attr(test_mode_capable, "%d\n");
+sdw_slave_attr(clk_stop_mode1, "%d\n");
+sdw_slave_attr(simple_clk_stop_capable, "%d\n");
+sdw_slave_attr(clk_stop_timeout, "%d\n");
+sdw_slave_attr(ch_prep_timeout, "%d\n");
+sdw_slave_attr(reset_behave, "%d\n");
+sdw_slave_attr(high_PHY_capable, "%d\n");
+sdw_slave_attr(paging_support, "%d\n");
+sdw_slave_attr(bank_delay_support, "%d\n");
+sdw_slave_attr(p15_behave, "%d\n");
+sdw_slave_attr(master_count, "%d\n");
+sdw_slave_attr(source_ports, "0x%x\n");
+sdw_slave_attr(sink_ports, "0x%x\n");
+
+static ssize_t modalias_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+
+ return sdw_slave_modalias(slave, buf, 256);
+}
+static DEVICE_ATTR_RO(modalias);
+
+static struct attribute *slave_attrs[] = {
+ &dev_attr_modalias.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(slave);
+
+static struct attribute *slave_dev_attrs[] = {
+ &dev_attr_mipi_revision.attr,
+ &dev_attr_wake_capable.attr,
+ &dev_attr_test_mode_capable.attr,
+ &dev_attr_clk_stop_mode1.attr,
+ &dev_attr_simple_clk_stop_capable.attr,
+ &dev_attr_clk_stop_timeout.attr,
+ &dev_attr_ch_prep_timeout.attr,
+ &dev_attr_reset_behave.attr,
+ &dev_attr_high_PHY_capable.attr,
+ &dev_attr_paging_support.attr,
+ &dev_attr_bank_delay_support.attr,
+ &dev_attr_p15_behave.attr,
+ &dev_attr_master_count.attr,
+ &dev_attr_source_ports.attr,
+ &dev_attr_sink_ports.attr,
+ NULL,
+};
+
+/*
+ * we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory
+ * for device-level properties
+ */
+static struct attribute_group sdw_slave_dev_attr_group = {
+ .attrs = slave_dev_attrs,
+ .name = "dev-properties",
+};
+
+/*
+ * DP0 sysfs
+ */
+
+#define sdw_dp0_attr(field, format_string) \
+static ssize_t field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct sdw_slave *slave = dev_to_sdw_dev(dev); \
+ return sprintf(buf, format_string, slave->prop.dp0_prop->field);\
+} \
+static DEVICE_ATTR_RO(field)
+
+sdw_dp0_attr(max_word, "%d\n");
+sdw_dp0_attr(min_word, "%d\n");
+sdw_dp0_attr(BRA_flow_controlled, "%d\n");
+sdw_dp0_attr(simple_ch_prep_sm, "%d\n");
+sdw_dp0_attr(imp_def_interrupts, "0x%x\n");
+
+static ssize_t words_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ ssize_t size = 0;
+ int i;
+
+ for (i = 0; i < slave->prop.dp0_prop->num_words; i++)
+ size += sprintf(buf + size, "%d ",
+ slave->prop.dp0_prop->words[i]);
+ size += sprintf(buf + size, "\n");
+
+ return size;
+}
+static DEVICE_ATTR_RO(words);
+
+static struct attribute *dp0_attrs[] = {
+ &dev_attr_max_word.attr,
+ &dev_attr_min_word.attr,
+ &dev_attr_words.attr,
+ &dev_attr_BRA_flow_controlled.attr,
+ &dev_attr_simple_ch_prep_sm.attr,
+ &dev_attr_imp_def_interrupts.attr,
+ NULL,
+};
+
+/*
+ * we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory
+ * for dp0-level properties
+ */
+static const struct attribute_group dp0_group = {
+ .attrs = dp0_attrs,
+ .name = "dp0",
+};
+
+int sdw_slave_sysfs_init(struct sdw_slave *slave)
+{
+ int ret;
+
+ ret = devm_device_add_groups(&slave->dev, slave_groups);
+ if (ret < 0)
+ return ret;
+
+ ret = devm_device_add_group(&slave->dev, &sdw_slave_dev_attr_group);
+ if (ret < 0)
+ return ret;
+
+ if (slave->prop.dp0_prop) {
+ ret = devm_device_add_group(&slave->dev, &dp0_group);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (slave->prop.source_ports || slave->prop.sink_ports) {
+ ret = sdw_slave_sysfs_dpn_init(slave);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/drivers/soundwire/sysfs_slave_dpn.c b/drivers/soundwire/sysfs_slave_dpn.c
new file mode 100644
index 000000000000..05a721ea9830
--- /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;
+
+ /* paranoia 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;
+}
diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h
index 00f5826092e3..9c27a32df9bb 100644
--- a/include/linux/soundwire/sdw.h
+++ b/include/linux/soundwire/sdw.h
@@ -291,8 +291,8 @@ struct sdw_dpn_audio_mode {
* implementation-defined interrupts
* @max_ch: Maximum channels supported
* @min_ch: Minimum channels supported
- * @num_ch: Number of discrete channels supported
- * @ch: Discrete channels supported
+ * @num_channels: Number of discrete channels supported
+ * @channels: Discrete channels supported
* @num_ch_combinations: Number of channel combinations supported
* @ch_combinations: Channel combinations supported
* @modes: SDW mode supported
@@ -316,8 +316,8 @@ struct sdw_dpn_prop {
u32 imp_def_interrupts;
u32 max_ch;
u32 min_ch;
- u32 num_ch;
- u32 *ch;
+ u32 num_channels;
+ u32 *channels;
u32 num_ch_combinations;
u32 *ch_combinations;
u32 modes;
@@ -632,6 +632,19 @@ struct sdw_slave {
#define dev_to_sdw_dev(_dev) container_of(_dev, struct sdw_slave, dev)
+/**
+ * struct sdw_master_device - SoundWire 'Master Device' representation
+ * @dev: Linux device for this Master
+ * @bus: Bus handle shortcut
+ */
+struct sdw_master_device {
+ struct device dev;
+ struct sdw_bus *bus;
+};
+
+#define dev_to_sdw_master_device(d) \
+ container_of(d, struct sdw_master_device, dev)
+
struct sdw_driver {
const char *name;
@@ -787,8 +800,10 @@ struct sdw_master_ops {
/**
* struct sdw_bus - SoundWire bus
- * @dev: Master linux device
+ * @dev: Shortcut to &bus->md->dev to avoid changing the entire code.
+ * @md: Master device
* @link_id: Link id number, can be 0 to N, unique for each Master
+ * @id: bus system-wide unique id
* @slaves: list of Slaves on this bus
* @assigned: Bitmap for Slave device numbers.
* Bit set implies used number, bit clear implies unused number.
@@ -812,7 +827,9 @@ struct sdw_master_ops {
*/
struct sdw_bus {
struct device *dev;
+ struct sdw_master_device *md;
unsigned int link_id;
+ int id;
struct list_head slaves;
DECLARE_BITMAP(assigned, SDW_MAX_DEVICES);
struct mutex bus_lock;
@@ -832,8 +849,9 @@ struct sdw_bus {
bool multi_link;
};
-int sdw_add_bus_master(struct sdw_bus *bus);
-void sdw_delete_bus_master(struct sdw_bus *bus);
+int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent,
+ struct fwnode_handle *fwnode);
+void sdw_bus_master_delete(struct sdw_bus *bus);
/**
* sdw_port_config: Master or Slave Port configuration
diff --git a/include/linux/soundwire/sdw_type.h b/include/linux/soundwire/sdw_type.h
index aaa7f4267c14..52eb66cd11bc 100644
--- a/include/linux/soundwire/sdw_type.h
+++ b/include/linux/soundwire/sdw_type.h
@@ -5,6 +5,13 @@
#define __SOUNDWIRE_TYPES_H
extern struct bus_type sdw_bus_type;
+extern struct device_type sdw_slave_type;
+extern struct device_type sdw_master_type;
+
+static inline int is_sdw_slave(const struct device *dev)
+{
+ return dev->type == &sdw_slave_type;
+}
#define drv_to_sdw_driver(_drv) container_of(_drv, struct sdw_driver, driver)
@@ -14,7 +21,7 @@ extern struct bus_type sdw_bus_type;
int __sdw_register_driver(struct sdw_driver *drv, struct module *owner);
void sdw_unregister_driver(struct sdw_driver *drv);
-int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size);
+int sdw_slave_uevent(struct device *dev, struct kobj_uevent_env *env);
/**
* module_sdw_driver() - Helper macro for registering a Soundwire driver