diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-05-22 09:25:25 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-05-22 09:25:25 +0200 |
commit | ddc0aef01a90ee8431f1a47f7b35e84d36ab8913 (patch) | |
tree | e0fd502efbaa74c955d271ea170d9e3268433874 | |
parent | c9cf27d9dee20193660b6582e4e6d6b315bff217 (diff) | |
parent | fcbcf1f7b56855ffe1cec768cb1013c5a4c854c3 (diff) | |
download | linux-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-master | 23 | ||||
-rw-r--r-- | Documentation/ABI/testing/sysfs-bus-soundwire-slave | 91 | ||||
-rw-r--r-- | Documentation/driver-api/soundwire/stream.rst | 89 | ||||
-rw-r--r-- | Documentation/driver-api/soundwire/summary.rst | 7 | ||||
-rw-r--r-- | drivers/soundwire/Makefile | 8 | ||||
-rw-r--r-- | drivers/soundwire/bus.c | 71 | ||||
-rw-r--r-- | drivers/soundwire/bus.h | 4 | ||||
-rw-r--r-- | drivers/soundwire/bus_type.c | 22 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.c | 8 | ||||
-rw-r--r-- | drivers/soundwire/debugfs.c | 2 | ||||
-rw-r--r-- | drivers/soundwire/intel.c | 13 | ||||
-rw-r--r-- | drivers/soundwire/intel_init.c | 4 | ||||
-rw-r--r-- | drivers/soundwire/master.c | 172 | ||||
-rw-r--r-- | drivers/soundwire/mipi_disco.c | 11 | ||||
-rw-r--r-- | drivers/soundwire/qcom.c | 34 | ||||
-rw-r--r-- | drivers/soundwire/slave.c | 10 | ||||
-rw-r--r-- | drivers/soundwire/sysfs_local.h | 14 | ||||
-rw-r--r-- | drivers/soundwire/sysfs_slave.c | 214 | ||||
-rw-r--r-- | drivers/soundwire/sysfs_slave_dpn.c | 300 | ||||
-rw-r--r-- | include/linux/soundwire/sdw.h | 32 | ||||
-rw-r--r-- | include/linux/soundwire/sdw_type.h | 9 |
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 |