[alsa-devel] [RFC 00/14] SoundWire bus driver
Following RFC series adds SoundWire bus driver interface based on the MIPI SoundWire specification 1.1
The SoundWire protocol is a robust, scalable, low complexity, low power, low latency, two-pin (clock and data) multi-drop bus that allows for the transfer of multiple audio streams and embedded control/commands. SoundWire provides synchronization capabilities and supports both PCM and PDM, multichannel data, isochronous and asynchronous modes.
SoundWire does borrow a number of concepts from existing interfaces such as HDAudio, AC97, SLIMbus, which already provide control/audio on the same wires, or legacy interfaces such as I2C/I2S, TDM, PDM.
The capabilities of SoundWire make it unique however in that it can be implemented in peripherals such as microphones or amplifiers, mix PCM and PDM formats and enable clock scaling to reduce power consumption.
More details about the SoundWire protocol can be obtained from MIPI website as listed below (accessible to members only).
1. http://mipi.org/learning-center/webinars 2. https://members.mipi.org/wg/All-Members/document/download/65078 3. https://members.mipi.org/wg/Contributors/document/70055
Hardik Shah (12): SoundWire: Add SoundWire bus driver documentation SoundWire: Add SoundWire stream documentation SoundWire: Add error handling and locking documentation SoundWire: Add device_id table for SoundWire bus SoundWire: Add SoundWire bus driver interfaces SoundWire: Add register/unregister APIs SoundWire: Add SoundWire Slaves register definitions SoundWire: Add API for Slave registers read/write SoundWire: Add support to handle Slave status change SoundWire: Add support for clock stop SoundWire: Add tracing for Slave register read/write regmap: SoundWire: Add regmap support for SoundWire bus
Sanyog Kale (2): SoundWire: Add stream and port configuration SoundWire: Add support for SoundWire stream management
Documentation/sound/alsa/sdw/error_handling.txt | 71 + Documentation/sound/alsa/sdw/locking.txt | 64 + Documentation/sound/alsa/sdw/stream.txt | 346 +++ Documentation/sound/alsa/sdw/summary.txt | 253 ++ MAINTAINERS | 12 + drivers/base/regmap/Kconfig | 3 + drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-sdw.c | 240 ++ include/linux/mod_devicetable.h | 13 + include/linux/regmap.h | 37 + include/sound/sdw/sdw_registers.h | 277 ++ include/sound/sdw_bus.h | 905 ++++++ include/sound/sdw_master.h | 627 ++++ include/sound/sdw_slave.h | 563 ++++ include/trace/events/sdw.h | 209 ++ sound/Kconfig | 2 + sound/Makefile | 1 + sound/sdw/Kconfig | 6 + sound/sdw/Makefile | 1 + sound/sdw/sdw.c | 3749 +++++++++++++++++++++++ sound/sdw/sdw_priv.h | 811 +++++ sound/sdw/sdw_runtime.c | 2807 +++++++++++++++++ 22 files changed, 10998 insertions(+) create mode 100644 Documentation/sound/alsa/sdw/error_handling.txt create mode 100644 Documentation/sound/alsa/sdw/locking.txt create mode 100644 Documentation/sound/alsa/sdw/stream.txt create mode 100644 Documentation/sound/alsa/sdw/summary.txt create mode 100644 drivers/base/regmap/regmap-sdw.c create mode 100644 include/sound/sdw/sdw_registers.h create mode 100644 include/sound/sdw_bus.h create mode 100644 include/sound/sdw_master.h create mode 100644 include/sound/sdw_slave.h create mode 100644 include/trace/events/sdw.h create mode 100644 sound/sdw/Kconfig create mode 100644 sound/sdw/Makefile create mode 100644 sound/sdw/sdw.c create mode 100644 sound/sdw/sdw_priv.h create mode 100644 sound/sdw/sdw_runtime.c
This patch adds summary documentation of SoundWire bus driver support in Linux.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- Documentation/sound/alsa/sdw/summary.txt | 253 ++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 Documentation/sound/alsa/sdw/summary.txt
diff --git a/Documentation/sound/alsa/sdw/summary.txt b/Documentation/sound/alsa/sdw/summary.txt new file mode 100644 index 0000000..dc62817 --- /dev/null +++ b/Documentation/sound/alsa/sdw/summary.txt @@ -0,0 +1,253 @@ +SoundWire +=========== + +SoundWire is a new interface ratified in 2015 by the MIPI Alliance. +SoundWire is used for transporting data typically related to audio +functions. SoundWire interface is optimized to integrate audio devices +in mobile or mobile inspired systems. + +SoundWire is a 2-Pin interface with data and clock line. It facilitates +development of low cost, efficient, high performance systems. Broad +level key features of SoundWire interface include: + +1. Transporting all of payload data channels, control information, and +setup commands over a single two-pin interface. +2. Lower clock frequency, and hence lower power consumption, by use of +DDR (Dual Data Rate) data transmission. +3. Clock scaling and optional multiple data lanes to give wide +flexibility in data rate to match system requirements. +4. Device status monitoring, including interrupt-style alerts to the +Master. + +The SoundWire protocol supports up to eleven Slave interfaces. All the +interfaces share the common bus containing data and clock line. Each of +the Slaves can support up to 14 Data Ports. 13 Data Ports are dedicated +to audio transport. Data Port0 is dedicated to transport of Bulk control +information, each of the audio Data Ports (1..14) can support up to 8 +Channels in transmit or receiving mode (typically fixed direction but +configurable direction is enabled by the specification). Bandwidth +restrictions to ~19.2..24.576Mbits/s don't however allow for 11*13*8 +channels to be transmitted simultaneously. + +Below figure shows the SoundWire Master and Slave devices sample +connectivity. + +|---------------| Clock Signal |---------------| +| Master |-------|-------------------------------| Slave | +| Interface | | Data Signal | Interface | +| |-------|-------|-----------------------| 1 | +|---------------| | | |---------------| + | | + | | + | | + |---------------| + | Slave | + | Interface | + | 2 | + |---------------| + +Terminology +============= + +Following is the terminology used in Linux kernel driver. SoundWire +Master interfaces registers as SoundWire Master device and Slave +interfaces as SoundWire Slave device. + +Bus: +Similar to I2C/AC97 bus driver, implements Linux bus for SoundWire +handles the SoundWire protocol and manages bus. Programs all the MIPI +defined Slave registers. + +Master: +Registers as SoundWire Master device (Linux Device). Similar to +i2c_adapter. One bus instance is created for every Master interface +registered to the bus driver. + +Slave: +Registers as SoundWire Slave device (Linux Device). Similar to +i2c_client. Multiple Slave interfaces can register to same bus. + +Master driver: +Driver controlling the Master device. Registers a set of ops to abstract +Master registers away, so that the bus driver can control the bus in a +hardware-agnostic manner. + +Slave driver: +Driver controlling the Slave device. MIPI-specified registers are +controlled directly by the bus driver (and transmitted through the +Master driver/interface). Any implementation-defined Slave register is +controlled by Slave driver. In practice, it is expected that the Slave +driver relies on regmap and does not request direct register access. + + +Programming interfaces (SoundWire Master interface Driver) +========================================================== + +SoundWire bus driver supports programming interfaces for the SoundWire +Master and SoundWire Slave devices. All the code uses the "sdw" prefix +commonly used by SOC designers and 3rd party vendors. + +Each of the SoundWire Master interface needs to be registered to the Bus +driver. Master interface capabilities also needs to be registered to +bus driver since there is no discovery mechanism as a part of SoundWire +protocol. + +The Master interface along with the Master interface capabilities are +registered based on board file, DT or ACPI. + +Following is the API to register the SoundWire Master device. + +static int my_sdw_register_master() +{ + struct sdw_master master; + struct sdw_master_capabilities *m_cap; + + m_cap = &master.mstr_capabilities; + + /* + * Fill the Master device capability, this is required + * by bus driver to handle bus configurations. + */ + m_cap->highphy_capable = false; + m_cap->monitor_handover_supported = false; + m_cap->sdw_dp0_supported = 1; + m_cap->num_data_ports = INTEL_SDW_MAX_PORTS; + + return snd_sdw_master_add(&master); +} + +Master driver gets registered for controlling the Master device. It +provides the callback functions to the bus driver to control the bus in +device specific way. Device and Driver binds according to the standard +Linux device-driver bind model. Master driver is registered from the +driver init code. Below code shows the sample Master driver +registration. + +static struct sdw_master_driver intel_sdw_mstr_driver = { + .driver_type = SDW_DRIVER_TYPE_MASTER, + .driver = { + .name = "intel_sdw_mstr", + .pm = &intel_sdw_pm_ops, + }, + + .probe = intel_sdw_probe, + .remove = intel_sdw_remove, + .mstr_ops = &intel_sdw_master_ops, + .mstr_port_ops = &intel_sdw_master_port_ops, +}; + +static int __init intel_sdw_init(void) { + return snd_sdw_master_register_driver(&intel_sdw_mstr_driver); +} + +As shown above Master driver registers itself with bus using +"sdw_mstr_driver_register" API, It registers using set of "mstr_ops" and +"mstr_port_ops" callback functions to the bus driver. + +"mstr_ops" is used by bus driver to control the bus in the hardware +specific way. It includes bus control functions such as sending the +SoundWire read/write messages on bus. The Bus driver also defines the +clock frequency and frameshape allocation needed by active stream and +configuration messages that need to be transmitted over the bus, to +maximize the bandwidth needed while minimizing the power. The "mstr_ops" +structure abstracts the hardware details of the Master from the bus +driver for setting up of the clock frequency and frameshape. + +"mstr_port_ops" is used by bus driver to setup the Port parameters of +the Master interface Port. Master interface Port register map is not +defined by MIPI specification, so bus driver calls the "mstr_port_ops" +call back function to do Port operations like "Port Prepare", "Port +Transport params set", "Port enable and disable". The implementation of +the Master driver can then perform hardware-specific configurations. + +Programming interfaces (SoundWire Slave Driver) +=============================================== + +The MIPI specification requires each Slave interface to expose a unique +48-bit identifier, stored in 6 read only dev_id registers. This dev_id +identifier contains vendor and part information, as well as a field +enabling to differentiate between identical components. An additional +class field is currently unused. Slave driver is written for the +specific 48-bit identifier, Bus driver enumerates the Slave device based +on in 48-bit identifier. Slave device and driver match is done based on +this 48-bit identifier. Probe of the Slave driver is called by bus on +successful match between device and driver id. A parent/child +relationship is enforced between Slave and Master devices (the logical +representation is aligned with the physical connectivity). + +The information on Master/Slave dependencies is stored in platform data, +board-file, ACPI or DT. The MIPI Software specification defines an +additional link_id parameters for controllers that have multiple Master +interfaces. The dev_id registers are only unique in the scope of a link, +and the link_ID unique in the scope of a controller. Both dev_id and +link_id are not necessarily unique at the system level but the +parent/child information is used to avoid ambiguity. + + +static const struct sdw_slave_id intel_id[] = { + {"0b:00:f9:84:01:02", 0}, + {}, +}; + +MODULE_DEVICE_TABLE(sdw, intel_id); + +static struct sdw_slave_driver intel_sdw_driver = { + .driver_type = SDW_DRIVER_TYPE_SLAVE, + .driver = { + .name = "intel", + }, + .probe = intel_sdw_probe, + .remove = intel_sdw_remove, + .id_table = intel_id, +}; + +module_sdw_slave_driver(intel_sdw_driver); + +Slave driver needs to register the capabilities (number of ports, +formats supported, etc) of the Slave device to the bus driver after +registration. This is the first call to be called by Slave driver on +probe. Bus driver needs to know a set of Slave capabilities to program +Slave registers and to control the bus reconfigurations. + +Below code shows the sample for it. Bus drivers programs the MIPI +defined registers of the Slave. + +static int slave_register_sdw_capabilities(struct sdw_slave *sdw, + const struct sdw_slave_id *sdw_id) +{ + struct sdw_slv_capabilities cap; + struct sdw_slv_dpn_capabilities *dpn_cap = NULL; + struct port_audio_mode_properties *prop = NULL; + int i, j; + + cap.wake_up_unavailable = true; + cap.test_mode_supported = false; + cap.clock_stop1_mode_supported = false; + cap.simplified_clock_stop_prepare = false; + cap.highphy_capable = true; + cap.paging_supported = false; + cap.bank_delay_support = false; + cap.port_15_read_behavior = 0; + cap.sdw_dp0_supported = false; + cap.num_of_sdw_ports = 3; + [...] additional configuration. + + return snd_sdw_slave_register_caps(sdw, &cap); + +} + +Future enhancements to be done: +=============================== + +1. Currently BRA transfers is not supported. +2. Bus driver supports only Single Data lane Slaves and Masters +interfaces. + +Links: +===== + +SoundWire MIPI specification 1.1 is available at: +https://members.mipi.org/wg/All-Members/document/70290 + +SoundWire MIPI DisCo (Discovery and Configuration) specification is in +press.
On Fri, Oct 21, 2016 at 06:10:59PM +0530, Hardik Shah wrote:
This patch adds summary documentation of SoundWire bus driver support in Linux.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
Documentation/sound/alsa/sdw/summary.txt | 253 ++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 Documentation/sound/alsa/sdw/summary.txt
diff --git a/Documentation/sound/alsa/sdw/summary.txt b/Documentation/sound/alsa/sdw/summary.txt new file mode 100644 index 0000000..dc62817 --- /dev/null +++ b/Documentation/sound/alsa/sdw/summary.txt @@ -0,0 +1,253 @@
<snip>
+Programming interfaces (SoundWire Master interface Driver) +==========================================================
+SoundWire bus driver supports programming interfaces for the SoundWire +Master and SoundWire Slave devices. All the code uses the "sdw" prefix +commonly used by SOC designers and 3rd party vendors.
+Each of the SoundWire Master interface needs to be registered to the Bus +driver. Master interface capabilities also needs to be registered to +bus driver since there is no discovery mechanism as a part of SoundWire +protocol.
+The Master interface along with the Master interface capabilities are +registered based on board file, DT or ACPI.
+Following is the API to register the SoundWire Master device.
+static int my_sdw_register_master() +{
- struct sdw_master master;
- struct sdw_master_capabilities *m_cap;
- m_cap = &master.mstr_capabilities;
- /*
* Fill the Master device capability, this is required
* by bus driver to handle bus configurations.
*/
- m_cap->highphy_capable = false;
- m_cap->monitor_handover_supported = false;
- m_cap->sdw_dp0_supported = 1;
- m_cap->num_data_ports = INTEL_SDW_MAX_PORTS;
- return snd_sdw_master_add(&master);
+}
+Master driver gets registered for controlling the Master device. It +provides the callback functions to the bus driver to control the bus in +device specific way. Device and Driver binds according to the standard +Linux device-driver bind model. Master driver is registered from the +driver init code. Below code shows the sample Master driver +registration.
+static struct sdw_master_driver intel_sdw_mstr_driver = {
- .driver_type = SDW_DRIVER_TYPE_MASTER,
- .driver = {
.name = "intel_sdw_mstr",
.pm = &intel_sdw_pm_ops,
- },
- .probe = intel_sdw_probe,
- .remove = intel_sdw_remove,
- .mstr_ops = &intel_sdw_master_ops,
- .mstr_port_ops = &intel_sdw_master_port_ops,
+};
+static int __init intel_sdw_init(void) {
- return snd_sdw_master_register_driver(&intel_sdw_mstr_driver);
+}
Would be good to hear some detail the reasoning for the design choices here? Normally (I2C/SPI) the master sits on whatever bus the host uses to talk to the master so often this might be the platform bus for memory mapped devices, it then creates a bus and slaves register to that. This also has the nice property that its easy to create devices that sit behind other buses, for example here we might want a SoundWire master that sits behind a SPI bus. But you seem to have gone in the other direction and have the master sitting on the same bus as the slaves.
+As shown above Master driver registers itself with bus using +"sdw_mstr_driver_register" API, It registers using set of "mstr_ops" and +"mstr_port_ops" callback functions to the bus driver.
+"mstr_ops" is used by bus driver to control the bus in the hardware +specific way. It includes bus control functions such as sending the +SoundWire read/write messages on bus. The Bus driver also defines the +clock frequency and frameshape allocation needed by active stream and +configuration messages that need to be transmitted over the bus, to +maximize the bandwidth needed while minimizing the power. The "mstr_ops" +structure abstracts the hardware details of the Master from the bus +driver for setting up of the clock frequency and frameshape.
+"mstr_port_ops" is used by bus driver to setup the Port parameters of +the Master interface Port. Master interface Port register map is not +defined by MIPI specification, so bus driver calls the "mstr_port_ops" +call back function to do Port operations like "Port Prepare", "Port +Transport params set", "Port enable and disable". The implementation of +the Master driver can then perform hardware-specific configurations.
Thanks, Charles
On Mon, Nov 14, 2016 at 02:15:48PM +0000, Charles Keepax wrote:
+static int my_sdw_register_master() +{
- struct sdw_master master;
- struct sdw_master_capabilities *m_cap;
- m_cap = &master.mstr_capabilities;
- /*
* Fill the Master device capability, this is required
* by bus driver to handle bus configurations.
*/
- m_cap->highphy_capable = false;
- m_cap->monitor_handover_supported = false;
- m_cap->sdw_dp0_supported = 1;
- m_cap->num_data_ports = INTEL_SDW_MAX_PORTS;
- return snd_sdw_master_add(&master);
+}
+Master driver gets registered for controlling the Master device. It +provides the callback functions to the bus driver to control the bus in +device specific way. Device and Driver binds according to the standard +Linux device-driver bind model. Master driver is registered from the +driver init code. Below code shows the sample Master driver +registration.
+static struct sdw_master_driver intel_sdw_mstr_driver = {
- .driver_type = SDW_DRIVER_TYPE_MASTER,
- .driver = {
.name = "intel_sdw_mstr",
.pm = &intel_sdw_pm_ops,
- },
- .probe = intel_sdw_probe,
- .remove = intel_sdw_remove,
- .mstr_ops = &intel_sdw_master_ops,
- .mstr_port_ops = &intel_sdw_master_port_ops,
+};
+static int __init intel_sdw_init(void) {
- return snd_sdw_master_register_driver(&intel_sdw_mstr_driver);
+}
Would be good to hear some detail the reasoning for the design choices here? Normally (I2C/SPI) the master sits on whatever bus the host uses to talk to the master so often this might be the platform bus for memory mapped devices, it then creates a bus and slaves register to that. This also has the nice property that its easy to create devices that sit behind other buses, for example here we might want a SoundWire master that sits behind a SPI bus. But you seem to have gone in the other direction and have the master sitting on the same bus as the slaves.
Since the controller on our SoC was enumerable, people went with this approach. In this hindsight that may not have been the best choice.
So it will be fixed in next rev.
On Tue, Nov 15, 2016 at 07:59:14PM +0530, Vinod Koul wrote:
On Mon, Nov 14, 2016 at 02:15:48PM +0000, Charles Keepax wrote:
slaves register to that. This also has the nice property that its easy to create devices that sit behind other buses, for example here we might want a SoundWire master that sits behind a SPI bus. But you seem to have gone in the other direction and have the master sitting on the same bus as the slaves.
Since the controller on our SoC was enumerable, people went with this approach. In this hindsight that may not have been the best choice.
Doing buses properly isn't an obstacle to doing enumeration, indeed I'd expect it to make it a lot easier - just have your driver for your controller do the enumeration at probe time.
On Wed, Nov 16, 2016 at 05:59:29PM +0000, Mark Brown wrote:
On Tue, Nov 15, 2016 at 07:59:14PM +0530, Vinod Koul wrote:
On Mon, Nov 14, 2016 at 02:15:48PM +0000, Charles Keepax wrote:
slaves register to that. This also has the nice property that its easy to create devices that sit behind other buses, for example here we might want a SoundWire master that sits behind a SPI bus. But you seem to have gone in the other direction and have the master sitting on the same bus as the slaves.
Since the controller on our SoC was enumerable, people went with this approach. In this hindsight that may not have been the best choice.
Doing buses properly isn't an obstacle to doing enumeration, indeed I'd expect it to make it a lot easier - just have your driver for your controller do the enumeration at probe time.
Yes it is not :) This is somthing we are fixing now..
This patch adds stream documentation describing SoundWire stream and stream states.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- Documentation/sound/alsa/sdw/stream.txt | 346 +++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 Documentation/sound/alsa/sdw/stream.txt
diff --git a/Documentation/sound/alsa/sdw/stream.txt b/Documentation/sound/alsa/sdw/stream.txt new file mode 100644 index 0000000..a1a2ed0 --- /dev/null +++ b/Documentation/sound/alsa/sdw/stream.txt @@ -0,0 +1,346 @@ +Audio Stream in SoundWire +========================= +An audio stream is a logical or virtual connection created between + +1. System memory buffer(s) and Codec(s) +2. DSP memory buffer(s) and Codec(s) +3. FIFO(s) and Codec(s) +4. Codec(s) and Codec(s) + +which is typically driven by a DMA(s) channel through the data link. An +audio stream contains one or more channels of data. All channels within +stream must have same sample rate and same sample size. + +Assume a stream with two channels (Left & Right) is opened using +SoundWire interface. Below are some of different way a stream can be +represented in SoundWire. + +Stream Sample in memory (System memory, DSP memory or FIFOs): +------------------------- +| L | R | L | R | L | R | +------------------------- + +Example 1: Stereo Stream with L and R channels is rendered from Master +to Slave. Both Master and Slave is using single port. + ++---------------+ Clock Signal +---------------+ +| Master +---------------------------------------+ Slave | +| Interface | | Interface | +| | | 1 | +| | Data Signal | | +| L + R +---------------------------------------+ L + R | +| (Data) | Data Direction | (Data) | ++---------------+ +-----------------------> +---------------+ + + +Example 2: Stereo Stream with L and R channels is captured from Slave to +Master. Both Master and Slave is using single port. + + ++---------------+ Clock Signal +---------------+ +| Master +---------------------------------------+ Slave | +| Interface | | Interface | +| | | 1 | +| | Data Signal | | +| L + R +---------------------------------------+ L + R | +| (Data) | Data Direction | (Data) | ++---------------+ <-----------------------+ +---------------+ + + +Example 3: Stereo Stream with L and R channels is rendered by Master. +Each of the L and R channel is received by two different Slaves. Master +and both Slaves are using single port. + + ++---------------+ Clock Signal +---------------+ +| Master +------------+--------------------------+ Slave | +| Interface | | | Interface | +| | | | 1 | +| | | Data Signal | | +| L + R +------+--------------------------------+ L | +| (Data) | | | Data Direction | (Data) | ++---------------+ | | +-------------> +---------------+ + | | + | | + | | +---------------+ + | +------------------------> | Slave | + | | Interface | + | | 2 | + | | | + +------------------------------> | R | + | (Data) | + +---------------+ + + +Example 4: 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. + + ++---------------+ Clock Signal +---------------+ +| Master +---------------------------------------+ Slave | +| Interface | | Interface | +| 1 | | 1 | +| | Data Signal | | +| L +---------------------------------------+ L | +| (Data) | Data Direction | (Data) | ++---------------+ +-----------------------> +---------------+ + ++---------------+ Clock Signal +---------------+ +| Master +---------------------------------------+ Slave | +| Interface | | Interface | +| 2 | | 2 | +| | Data Signal | | +| R +---------------------------------------+ R | +| (Data) | Data Direction | (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. + + ++------------------------+ +| | +| +--------------+ +-------------------+ +| | || | | +| | Data Port || L Channel | | +| | 1 |------------+ | | +| | L Channel || | +-----+----+ | +| | (Data) || | L + R Channel || Data | | +| Master +--------------+ | +---+---------> || Port | | +| Interface | | || 1 | | +| +--------------+ | || | | +| | || | +----------+ | +| | Data Port |------------+ | | +| | 2 || R Channel | Slave | +| | R Channel || | Interface | +| | (Data) || | 1 | +| +--------------+ Clock Signal | L + R | +| +---------------------------> | (Data) | ++------------------------+ | | + +-------------------+ + + +Example 6: Stereo stream with L and R channel is rendered by Slave +Interface 1 to Slave Interface 2. Master is driving the Clock. Audio +stream data flow is from Slave interface 1 to Slave Interface 2. + + + +---------------+ ++---------------+ Clock Signal | | +| Master +--+----------------------------------> | Slave | +| Interface | | Data Signal | Interface | +| +--------+----------------------------+ | 1 | ++---------------+ | | | | + | | | L + R | + | | +----------------+ | (Data) | + | | | +---------------+ + | | | + | | | Data Direction + | | | +---------------+ + | | | | | + | | +----------------> | Slave | + | | | Interface | + | | Clock Signal | 2 | + | +------------------------------+ | + | Data Signal | L + R | + +------------------------------------+ (Data) | + +---------------+ + + +SoundWire Stream Management flow +================================ + +SoundWire Stream definitions: +1. Current stream: This is classified as the stream on which operation +has to be performed like prepare, enable, disable, de-prepare etc. + +2. Active stream: This is classified as the stream which is already +active on bus other than current stream. There can be multiple active +streams on the bus. + +SoundWire bus driver manages stream operations for each stream getting +rendered/captured on the SoundWire bus. + +This section explains what Bus driver operations are done for each of +the stream getting allocated/released on bus driver. Following are the +stream states maintained by the Bus driver for each of the audio stream +getting opened. + + +SoundWire stream states +======================= +Below figure shows the SoundWire stream states and possible state +transition diagram. + +|--------------| |-------------| |--------------| |--------------| +| ALLOC |---->| CONFIG |---->| PREPARE |---->| ENABLE | +| STATE | | STATE | | STATE | | STATE | +|--------------| |-------------| |--------------| |--------------| + ^ | + | | + | | + | | + | / + |--------------| |--------------| |--------------| + | RELEASE |<--------------------| DEPREPARE |<----| DISABLE | + | STATE | | STATE | | STATE | + |--------------| |--------------| |--------------| + + +SoundWire Stream State Operations +================================== +Below section explains the operations done by the bus driver on +Master(s) and Slave(s) as part of stream state transitions. + +SDW_STATE_STRM_ALLOC: Allocation state for stream. This is the entry +state of the stream. Operations performed before entering in this +state: +1. An unique stream tag is assigned to stream. This stream tag is used +as a reference for all the operations performed on stream. + +2. The resources required for holding stream runtime information are +allocated and initialized. This holds all stream related information +such as stream type (PCM/PDM) and parameters, Master and Slave interface +associated with the stream, reference counting, stream state etc. + +After all above operations are successful, stream state is set to +SDW_STATE_STRM_ALLOC. + + +SDW_STATE_STRM_CONFIG: Configuration state of stream. Operations +performed before entering in this state: +1. The resources allocated for stream information in +SDW_STATE_STRM_ALLOC state are updated. This includes stream parameters, +Masters and Slaves runtime information associated with the stream. + +2. All the Masters and Slaves associated with the stream updates the +port configuration to bus driver. This includes port numbers allocated +by Master(s) and Slave(s) for this stream. + +After all above operations are successful, stream state is set to +SDW_STATE_STRM_CONFIG. + + +SDW_STATE_STRM_PREPARE: Prepare state of stream. Operations performed +before entering in this state: +1. Bus parameters such as bandwidth, frame shape, clock frequency, SSP +interval are computed based on current stream as well as already active +streams on bus. Re-computation is required to accommodate current stream +on the bus. + +2. Transport parameters of all Master and Slave ports are computed for +the current as well as already active stream based on above calculated +frame shape and clock frequency. + +3. Computed bus and transport parameters are programmed in Master and +Slave registers. The banked registers programming is done on the +alternate bank (bank currently unused). Port channels are enabled for +the already active streams on the alternate bank (bank currently +unused). This is done in order to not to disrupt already active +stream(s). + +4. Once all the new values are programmed, bus initiates switch to +alternate bank. Once switch is successful, the port channels enabled on +previous bank for already active streams are disabled. + +5. Ports of Master and Slave for current stream are prepared. + +After all above operations are successful, stream state is set to +SDW_STATE_STRM_PREPARE. + + +SDW_STATE_STRM_ENABLE: Enable state of stream. Operations performed +before entering in this state: +1. All the values computed in SDW_STATE_STRM_PREPARE state are +programmed in alternate bank (bank currently unused). It includes +programming of already active streams as well. + +2. All the Master and Slave port channels for the current stream are +enabled on alternate bank (bank currently unused). + +3. Once all the new values are programmed, bus initiates switch to +alternate bank. Once the switch is successful, the port channels enabled +on previous bank for already active streams are disabled. + +After all above operations are successful, stream state is set to +SDW_STATE_STRM_ENABLE. + + +SDW_STATE_STRM_DISABLE --> Disable state of stream. Operations performed +before entering in this state: +1. Disable for Master and Slave ports channels is performed on on +alternate bank (bank currently unused) registers for current stream. + +2. All the current configuration of bus and Master and Slave ports are +programmed into alternate bank (bank currently unused). It includes +programming of already active streams port channels on alternate bank +(bank currently unused). + +3. Bus initiates switch to alternate bank. Once the switch is +successful, the port channels of current stream are disabled. All the +port channels enabled on previous bank for active stream are disabled. + +After all above operations are successful, stream state is set to +SDW_STATE_STRM_DISABLE. + + +SDW_STATE_STRM_DEPREPARE: De-prepare state of stream. Operations +performed before entering in this state: +1. Check the bandwidth required per Master. If its zero, de-prepare +current stream and move stream state to SDW_STATE_STRM_DEPREPARE, rest +of the steps are not required. If bandwidth required per Master is non +zero that means some more streams are running on Master and continue +with next step. + +2. Bus parameters and transport parameters are computed for the active +stream(s) on the given Master. + +3. All the computed values for active stream(s) are programmed into +alternate bank (bank currently unused) in Master and Slave registers. + +4. Bus initiates switch to alternate bank. Once the switch is +successful, all the port channels enabled on previous bank for active +stream are disabled. + +5. De-prepare ports of the Master and Slave associated with current +stream. + +After all above operations are successful, stream state is set to +SDW_STATE_DEPREPARE. + + +SDW_STATE_STRM_RELEASE: Release state of stream. Operations performed +before entering in this state: +1. Release port resources for all Master and Slave ports used for +current stream. + +2. Release Master and Slave runtime resources used for current stream. + +3. Release stream runtime resources used for current stream. + +After all above operations are successful, stream state is set to +SDW_STATE_STRM_RELEASE. + +Future Enhancements +=================== +1. Slave to Slave communication: Currently stream between Slaves is not +supported. Master should be always part of the stream(s). + +2. Stream Linking for synchronized start: Currently multiple streams +cannot be synchronously started together with single bank switch. This +may require ASoC framework changes as well. + +Not Supported +============= +1. A single port with multiple channels supported cannot be used between +two streams or across stream. For example a port with 4 channels cannot +be used to handle 2 independent stereo streams even though it's possible +in theory in SoundWire. + +2. Bandwidth allocation is done in contiguous slots for stream. +Non-contiguous slots created due to bandwidth fragmentation are not +taken care in bandwidth calculation.
On Fri, Oct 21, 2016 at 06:11:00PM +0530, Hardik Shah wrote:
This patch adds stream documentation describing SoundWire stream and stream states.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
Documentation/sound/alsa/sdw/stream.txt | 346 +++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 Documentation/sound/alsa/sdw/stream.txt
diff --git a/Documentation/sound/alsa/sdw/stream.txt b/Documentation/sound/alsa/sdw/stream.txt new file mode 100644 index 0000000..a1a2ed0 --- /dev/null +++ b/Documentation/sound/alsa/sdw/stream.txt @@ -0,0 +1,346 @@
<snip>
+SoundWire stream states +======================= +Below figure shows the SoundWire stream states and possible state +transition diagram.
+|--------------| |-------------| |--------------| |--------------| +| ALLOC |---->| CONFIG |---->| PREPARE |---->| ENABLE | +| STATE | | STATE | | STATE | | STATE | +|--------------| |-------------| |--------------| |--------------|
^ |
| |
| |
| |
| \/
- |--------------| |--------------| |--------------|
- | RELEASE |<--------------------| DEPREPARE |<----| DISABLE |
- | STATE | | STATE | | STATE |
- |--------------| |--------------| |--------------|
One minor comment, this looks very similar to the clock frameworks state model, but the clock framework calls it unprepare would there be some milage in aligning to?
+SoundWire Stream State Operations +================================== +Below section explains the operations done by the bus driver on +Master(s) and Slave(s) as part of stream state transitions.
+SDW_STATE_STRM_ALLOC: Allocation state for stream. This is the entry +state of the stream. Operations performed before entering in this +state: +1. An unique stream tag is assigned to stream. This stream tag is used
A unique
+as a reference for all the operations performed on stream.
+2. The resources required for holding stream runtime information are +allocated and initialized. This holds all stream related information +such as stream type (PCM/PDM) and parameters, Master and Slave interface +associated with the stream, reference counting, stream state etc.
+After all above operations are successful, stream state is set to +SDW_STATE_STRM_ALLOC.
+SDW_STATE_STRM_CONFIG: Configuration state of stream. Operations +performed before entering in this state: +1. The resources allocated for stream information in +SDW_STATE_STRM_ALLOC state are updated. This includes stream parameters, +Masters and Slaves runtime information associated with the stream.
+2. All the Masters and Slaves associated with the stream updates the +port configuration to bus driver. This includes port numbers allocated +by Master(s) and Slave(s) for this stream.
+After all above operations are successful, stream state is set to +SDW_STATE_STRM_CONFIG.
+SDW_STATE_STRM_PREPARE: Prepare state of stream. Operations performed +before entering in this state: +1. Bus parameters such as bandwidth, frame shape, clock frequency, SSP +interval are computed based on current stream as well as already active +streams on bus. Re-computation is required to accommodate current stream +on the bus.
+2. Transport parameters of all Master and Slave ports are computed for +the current as well as already active stream based on above calculated +frame shape and clock frequency.
+3. Computed bus and transport parameters are programmed in Master and +Slave registers. The banked registers programming is done on the +alternate bank (bank currently unused). Port channels are enabled for +the already active streams on the alternate bank (bank currently +unused). This is done in order to not to disrupt already active +stream(s).
+4. Once all the new values are programmed, bus initiates switch to +alternate bank. Once switch is successful, the port channels enabled on +previous bank for already active streams are disabled.
+5. Ports of Master and Slave for current stream are prepared.
+After all above operations are successful, stream state is set to +SDW_STATE_STRM_PREPARE.
+SDW_STATE_STRM_ENABLE: Enable state of stream. Operations performed +before entering in this state: +1. All the values computed in SDW_STATE_STRM_PREPARE state are +programmed in alternate bank (bank currently unused). It includes +programming of already active streams as well.
+2. All the Master and Slave port channels for the current stream are +enabled on alternate bank (bank currently unused).
This could probably use a little more explaination to show how it differs from step 3/4 in PREPARE, as it looks like all the computed values where applied there. I imagine this is just my lack of understanding rather than an actual issue but even looking at the code I am having a little difficulty tying up these two.
sdw_prepare_op - sdw_compute_params (prepare step 1/2) - sdw_program_params (prepare step 3) - sdw_update_bus_params (prepare step 4)
sdw_enable_op - sdw_program_params (enable step 1) - sdw_update_bus_params (enable step 2)
It looks like the params are still basically the same as they were when we called sdw_program_params in prepare.
+3. Once all the new values are programmed, bus initiates switch to +alternate bank. Once the switch is successful, the port channels enabled +on previous bank for already active streams are disabled.
+After all above operations are successful, stream state is set to +SDW_STATE_STRM_ENABLE.
Thanks, Charles
+SoundWire stream states +======================= +Below figure shows the SoundWire stream states and possible state +transition diagram.
+|--------------| |-------------| |--------------| |--------------| +| ALLOC |---->| CONFIG |---->| PREPARE |---->| ENABLE | +| STATE | | STATE | | STATE | | STATE | +|--------------| |-------------| |--------------| |--------------|
^ |
| |
| |
| |
| \/
- |--------------| |--------------| |--------------|
- | RELEASE |<--------------------| DEPREPARE |<----| DISABLE |
- | STATE | | STATE | | STATE |
- |--------------| |--------------| |--------------|
One minor comment, this looks very similar to the clock frameworks state model, but the clock framework calls it unprepare would there be some milage in aligning to?
The SoundWire spec uses de-prepare, e.g. "De-prepare_Finished" I'd rather stick to the wording between a spec and the implementation of said spec, rather than introduce a term/concept from an unrelated framework.
+SoundWire Stream State Operations +================================== +Below section explains the operations done by the bus driver on +Master(s) and Slave(s) as part of stream state transitions.
+SDW_STATE_STRM_ALLOC: Allocation state for stream. This is the entry +state of the stream. Operations performed before entering in this +state: +1. An unique stream tag is assigned to stream. This stream tag is used
A unique
ok
+as a reference for all the operations performed on stream.
+2. The resources required for holding stream runtime information are +allocated and initialized. This holds all stream related information +such as stream type (PCM/PDM) and parameters, Master and Slave interface +associated with the stream, reference counting, stream state etc.
+After all above operations are successful, stream state is set to +SDW_STATE_STRM_ALLOC.
+SDW_STATE_STRM_CONFIG: Configuration state of stream. Operations +performed before entering in this state: +1. The resources allocated for stream information in +SDW_STATE_STRM_ALLOC state are updated. This includes stream parameters, +Masters and Slaves runtime information associated with the stream.
+2. All the Masters and Slaves associated with the stream updates the +port configuration to bus driver. This includes port numbers allocated +by Master(s) and Slave(s) for this stream.
+After all above operations are successful, stream state is set to +SDW_STATE_STRM_CONFIG.
+SDW_STATE_STRM_PREPARE: Prepare state of stream. Operations performed +before entering in this state: +1. Bus parameters such as bandwidth, frame shape, clock frequency, SSP +interval are computed based on current stream as well as already active +streams on bus. Re-computation is required to accommodate current stream +on the bus.
+2. Transport parameters of all Master and Slave ports are computed for +the current as well as already active stream based on above calculated +frame shape and clock frequency.
+3. Computed bus and transport parameters are programmed in Master and +Slave registers. The banked registers programming is done on the +alternate bank (bank currently unused). Port channels are enabled for +the already active streams on the alternate bank (bank currently +unused). This is done in order to not to disrupt already active +stream(s).
+4. Once all the new values are programmed, bus initiates switch to +alternate bank. Once switch is successful, the port channels enabled on +previous bank for already active streams are disabled.
This last sentence makes no sense in this context, probably a copy/paste that shouldn't be there. The previously active streams remain active in this prepare step.
+5. Ports of Master and Slave for current stream are prepared.
+After all above operations are successful, stream state is set to +SDW_STATE_STRM_PREPARE.
+SDW_STATE_STRM_ENABLE: Enable state of stream. Operations performed +before entering in this state: +1. All the values computed in SDW_STATE_STRM_PREPARE state are +programmed in alternate bank (bank currently unused). It includes +programming of already active streams as well.
+2. All the Master and Slave port channels for the current stream are +enabled on alternate bank (bank currently unused).
This could probably use a little more explaination to show how it differs from step 3/4 in PREPARE, as it looks like all the computed values where applied there. I imagine this is just my lack of understanding rather than an actual issue but even looking at the code I am having a little difficulty tying up these two.
Yes, see above there was an extra sentence that isn't right.
sdw_prepare_op
- sdw_compute_params (prepare step 1/2)
- sdw_program_params (prepare step 3)
- sdw_update_bus_params (prepare step 4)
sdw_enable_op
- sdw_program_params (enable step 1)
- sdw_update_bus_params (enable step 2)
It looks like the params are still basically the same as they were when we called sdw_program_params in prepare.
The parameters are the same except for the channel-enable flags which are only programmed and activated via a bank switch in the enable step.
On Mon, Nov 14, 2016 at 10:50:10AM -0600, Pierre-Louis Bossart wrote:
+SoundWire stream states +======================= +Below figure shows the SoundWire stream states and possible state +transition diagram.
+|--------------| |-------------| |--------------| |--------------| +| ALLOC |---->| CONFIG |---->| PREPARE |---->| ENABLE | +| STATE | | STATE | | STATE | | STATE | +|--------------| |-------------| |--------------| |--------------|
^ |
| |
| |
| |
| \/
- |--------------| |--------------| |--------------|
- | RELEASE |<--------------------| DEPREPARE |<----| DISABLE |
- | STATE | | STATE | | STATE |
- |--------------| |--------------| |--------------|
One minor comment, this looks very similar to the clock frameworks state model, but the clock framework calls it unprepare would there be some milage in aligning to?
The SoundWire spec uses de-prepare, e.g. "De-prepare_Finished" I'd rather stick to the wording between a spec and the implementation of said spec, rather than introduce a term/concept from an unrelated framework.
Cool we should leave that as is then :-)
+4. Once all the new values are programmed, bus initiates switch to +alternate bank. Once switch is successful, the port channels enabled on +previous bank for already active streams are disabled.
This last sentence makes no sense in this context, probably a copy/paste that shouldn't be there. The previously active streams remain active in this prepare step.
+5. Ports of Master and Slave for current stream are prepared.
+After all above operations are successful, stream state is set to +SDW_STATE_STRM_PREPARE.
+SDW_STATE_STRM_ENABLE: Enable state of stream. Operations performed +before entering in this state: +1. All the values computed in SDW_STATE_STRM_PREPARE state are +programmed in alternate bank (bank currently unused). It includes +programming of already active streams as well.
+2. All the Master and Slave port channels for the current stream are +enabled on alternate bank (bank currently unused).
This could probably use a little more explaination to show how it differs from step 3/4 in PREPARE, as it looks like all the computed values where applied there. I imagine this is just my lack of understanding rather than an actual issue but even looking at the code I am having a little difficulty tying up these two.
Yes, see above there was an extra sentence that isn't right.
sdw_prepare_op
- sdw_compute_params (prepare step 1/2)
- sdw_program_params (prepare step 3)
- sdw_update_bus_params (prepare step 4)
sdw_enable_op
- sdw_program_params (enable step 1)
- sdw_update_bus_params (enable step 2)
It looks like the params are still basically the same as they were when we called sdw_program_params in prepare.
The parameters are the same except for the channel-enable flags which are only programmed and activated via a bank switch in the enable step.
Ah ok that is what is getting pushed out there, thanks for explaining.
Thanks, Charles
This patch adds following documentation: 1. Bus driver locking mechanism. 2. Bus driver error handling.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- Documentation/sound/alsa/sdw/error_handling.txt | 71 +++++++++++++++++++++++ Documentation/sound/alsa/sdw/locking.txt | 64 ++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 Documentation/sound/alsa/sdw/error_handling.txt create mode 100644 Documentation/sound/alsa/sdw/locking.txt
diff --git a/Documentation/sound/alsa/sdw/error_handling.txt b/Documentation/sound/alsa/sdw/error_handling.txt new file mode 100644 index 0000000..9441cfa --- /dev/null +++ b/Documentation/sound/alsa/sdw/error_handling.txt @@ -0,0 +1,71 @@ +The SoundWire PHY was designed with care and errors on the bus are going +to be very unlikely, and if they happen they should be limited to single +bit errors. Examples of this design can be found in the the +synchronization mechanism (sync loss after two errors) and short CRCs +used for the Bulk Register Access. + +The errors can be detected with multiple mechanisms: + +1. Bus clash or parity errors: This mechanism relies on low-level +detectors that are independent of the payload and usages, and they +cover both control and audio data. The current implementation only logs +such errors. Improvements could consist in invalidating an entire +programming sequence and restarting from a known position. In the case +of such errors happening outside of a control/command sequence, there is +no concealment or recovery for audio data enabled by the SoundWire +protocol, the location of the error will also impact its audibility +(most-significant bits will be more impacted in PCM), and after a number +of such errors are detected the bus might be reset. Note that bus +clashes due to programming errors (two streams using the same bit slots) +or electrical issues during the transmit/receive transition cannot be +distinguished, although a recurring bus clash when audio is enabled is a +clear hint of a bus allocation issue. The interrupt mechanism can also +help identify Slaves which detected a Bus Clash or a Parity Error, but +they may not be responsible for the errors so resetting them +individually is not a viable recovery strategy. + +2. Command status: Each command is associated with a status, which only +covers transmission of the data between devices. The ACK status +indicates that the command was received and will be executed by the end +of the current frame. A NAK indicates that the command was in error and +will not be applied. In case of a bad programming (command sent to +non-existent Slave or to a non-implemented register) or electrical +issue, no response signals the command was ignored. Some Master +implementations allow for a command to be retransmitted several times. +If the retransmission fails, backtracking and restarting the entire +programming sequence might be a solution. Alternatively some +implementations might directly issue a bus reset and re-enumerate all +devices. + +3. Timeouts: In a number of cases such as ChannelPrepare or +ClockStopPrepare, the bus driver is supposed to poll a register field +until it transitions to a NotFinished value of zero. The MIPI SoundWire +spec 1.1 does not define timeouts but the MIPI SoundWire DisCo document +adds recommendation on timeouts. If such configurations do not complete, +the driver will return a -ETIMEOUT. Such timeouts are symptoms of a +faulty Slave device and are likely impossible to recover from. + + +Errors during global reconfiguration sequences are extremely difficult +to handle: + +1. BankSwitch: An error during the last command issuing a BankSwitch is +difficult to backtrack from. Retransmitting the Bank Switch command +may be possible in a single segment setup, but this can lead to +synchronization problems when enabling multiple bus segments (a command +with side effects such as frame reconfiguration would be handled at +different times). A global hard-reset might be the best solution. + +2. ClockStop: If one Slave is not capable of engaging the clock stop and +the system still needs to suspend, the priority might be to suspend +anyway and reset the bus on startup. This would prevent any active +standby/always-on activity but would not impact power targets. + +Note that SoundWire does not provide a detection mechanism for writing +illegal values in valid registers. In a number of cases the standard +even mentions that the Slave might behave in implementation-defined +registers. The bus driver implementation does not provide a recovery +mechanism for such errors, Slave or Master driver implementers are +responsible for writing valid values in valid registers and implement +additional range checking if needed. + diff --git a/Documentation/sound/alsa/sdw/locking.txt b/Documentation/sound/alsa/sdw/locking.txt new file mode 100644 index 0000000..650162f --- /dev/null +++ b/Documentation/sound/alsa/sdw/locking.txt @@ -0,0 +1,64 @@ +This document explains locking mechanism of the SoundWire bus driver. +Following types of lock are used in SoundWire bus driver. + +1. Core lock +2. Master lock +3. Stream lock +4. Message lock + +1. Core lock: Global SoundWire bus driver lock. Core lock is used to +serialize each of the following operation(s) within SoundWire bus +driver. + - Addition and removal of Master. + - Acquire "Master lock" of each Master associated with the + aggregated stream. + + +2. Master lock: SoundWire bus instance lock. Master lock is used to +serialize each of the following operation(s) within SoundWire bus +instance. + - Addition and removal of Slave(s). + - Prepare and enable, disable and de-prepare. + + +3. Stream lock: SoundWire stream lock. Stream lock is used to serialize +access of stream data structure for a SoundWire stream. + + +4. Message lock: SoundWire message transfer lock. This lock is used to +serialize the message transfers(read/write) within the SoundWire bus +instance. + + +Lock Hierarchy +============== + +- Core lock is the parent of Master and Stream lock. +- Master lock is parent of Message lock. +- Master and Stream locks are independent of each other. + +Example +======= + +Below example shows how locks are acquired during prepare and enable +operation for aggregated stream. In this example, stream 1 is associated +with Master 1 and Master 2. + +1. Acquire Core lock. +2. Acquire Master 1 lock. +3. Acquire Master 2 lock. +4. Release Core lock. + +5. Prepare operation. + 5.1 Acquire Message lock. + 5.2 Transfer message on bus (Single message transfer example). + 5.3 Release Message lock. +6. Enable operation. + 6.1 Acquire Message lock. + 6.2 Transfer message on bus (Single message transfer example). + 6.3 Release Message lock. + +7. Acquire Core lock. +8. Release Master 1 lock. +9. Release Master 2 lock. +10. Release Core lock.
On Fri, Oct 21, 2016 at 06:11:01PM +0530, Hardik Shah wrote:
This patch adds following documentation: 1. Bus driver locking mechanism. 2. Bus driver error handling.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
Documentation/sound/alsa/sdw/error_handling.txt | 71 +++++++++++++++++++++++ Documentation/sound/alsa/sdw/locking.txt | 64 ++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 Documentation/sound/alsa/sdw/error_handling.txt create mode 100644 Documentation/sound/alsa/sdw/locking.txt
diff --git a/Documentation/sound/alsa/sdw/error_handling.txt b/Documentation/sound/alsa/sdw/error_handling.txt new file mode 100644 index 0000000..9441cfa --- /dev/null +++ b/Documentation/sound/alsa/sdw/error_handling.txt @@ -0,0 +1,71 @@
<snip>
diff --git a/Documentation/sound/alsa/sdw/locking.txt b/Documentation/sound/alsa/sdw/locking.txt new file mode 100644 index 0000000..650162f --- /dev/null +++ b/Documentation/sound/alsa/sdw/locking.txt @@ -0,0 +1,64 @@ +This document explains locking mechanism of the SoundWire bus driver. +Following types of lock are used in SoundWire bus driver.
+1. Core lock +2. Master lock +3. Stream lock +4. Message lock
+1. Core lock: Global SoundWire bus driver lock. Core lock is used to +serialize each of the following operation(s) within SoundWire bus +driver.
- Addition and removal of Master.
- Acquire "Master lock" of each Master associated with the
aggregated stream.
+2. Master lock: SoundWire bus instance lock. Master lock is used to +serialize each of the following operation(s) within SoundWire bus +instance.
- Addition and removal of Slave(s).
- Prepare and enable, disable and de-prepare.
+3. Stream lock: SoundWire stream lock. Stream lock is used to serialize +access of stream data structure for a SoundWire stream.
+4. Message lock: SoundWire message transfer lock. This lock is used to +serialize the message transfers(read/write) within the SoundWire bus +instance.
+Lock Hierarchy +==============
+- Core lock is the parent of Master and Stream lock. +- Master lock is parent of Message lock. +- Master and Stream locks are independent of each other.
A small diagram might be nice here, just would make it easier to see the hierarchy at a glance.
Thanks, Charles
On Mon, Nov 14, 2016 at 03:44:35PM +0000, Charles Keepax wrote:
+Lock Hierarchy +==============
+- Core lock is the parent of Master and Stream lock. +- Master lock is parent of Message lock. +- Master and Stream locks are independent of each other.
A small diagram might be nice here, just would make it easier to see the hierarchy at a glance.
Sure will add..
Add device_id table for the SoundWire Master and Slave devices and driver registration.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/linux/mod_devicetable.h | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index ed84c07..f443152 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -672,5 +672,18 @@ struct fsl_mc_device_id { const char obj_type[16]; };
+/* SoundWire */ +#define SOUNDWIRE_NAME_SIZE 32 +#define SOUNDWIRE_MODULE_PREFIX "sdw:" + +struct sdw_slave_id { + char name[SOUNDWIRE_NAME_SIZE]; + kernel_ulong_t driver_data; /* Data private to the driver */ +}; + +struct sdw_master_id { + char name[SOUNDWIRE_NAME_SIZE]; + kernel_ulong_t driver_data; /* Data private to the driver */ +};
#endif /* LINUX_MOD_DEVICETABLE_H */
This patch adds following SoundWire bus driver APIs.
1. Register SoundWire Master device and driver. 2. Register SoundWire Slave driver. 3. Register Slave device capabilities.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/Kconfig | 2 + sound/Makefile | 1 + sound/sdw/Kconfig | 6 + sound/sdw/Makefile | 1 + sound/sdw/sdw.c | 886 ++++++++++++++++++++++++++++++++++++++++++++++++++ sound/sdw/sdw_priv.h | 102 ++++++ 6 files changed, 998 insertions(+) create mode 100644 sound/sdw/Kconfig create mode 100644 sound/sdw/Makefile create mode 100644 sound/sdw/sdw.c create mode 100644 sound/sdw/sdw_priv.h
diff --git a/sound/Kconfig b/sound/Kconfig index 5a240e0..9f67cf7 100644 --- a/sound/Kconfig +++ b/sound/Kconfig @@ -108,6 +108,8 @@ source "sound/parisc/Kconfig"
source "sound/soc/Kconfig"
+source "sound/sdw/Kconfig" + endif # SND
menuconfig SOUND_PRIME diff --git a/sound/Makefile b/sound/Makefile index c41bdf5..914101e 100644 --- a/sound/Makefile +++ b/sound/Makefile @@ -3,6 +3,7 @@
obj-$(CONFIG_SOUND) += soundcore.o obj-$(CONFIG_SOUND_PRIME) += oss/ +obj-$(CONFIG_SOUND_SDW) += sdw/ obj-$(CONFIG_DMASOUND) += oss/ obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \ firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ diff --git a/sound/sdw/Kconfig b/sound/sdw/Kconfig new file mode 100644 index 0000000..052079f --- /dev/null +++ b/sound/sdw/Kconfig @@ -0,0 +1,6 @@ +config SOUND_SDW + tristate + help + SoundWire interface is typically used for transporting data + related to audio functions. Concerned drivers should "select" + this. diff --git a/sound/sdw/Makefile b/sound/sdw/Makefile new file mode 100644 index 0000000..6ed1881 --- /dev/null +++ b/sound/sdw/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_SOUND_SDW) += sdw.o diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c new file mode 100644 index 0000000..d4e79b8a --- /dev/null +++ b/sound/sdw/sdw.c @@ -0,0 +1,886 @@ +/* + * sdw.c - SoundWire bus driver implementation. + * + * Author: Hardik Shah hardik.t.shah@intel.com + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * + * BSD LICENSE + * + * Copyright(c) 2016 Intel Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <linux/pm_domain.h> +#include <sound/sdw_bus.h> +#include <sound/sdw_master.h> +#include <sound/sdw_slave.h> + +#include "sdw_priv.h" + +/* + * Global SoundWire core instance contains list of Masters registered, core + * lock and SoundWire stream tags. + */ +struct snd_sdw_core snd_sdw_core; + +static void sdw_slv_release(struct device *dev) +{ + kfree(to_sdw_slave(dev)); +} + +static void sdw_mstr_release(struct device *dev) +{ + struct sdw_master *mstr = to_sdw_master(dev); + + complete(&mstr->slv_released_complete); +} + +static struct device_type sdw_slv_type = { + .groups = NULL, + .release = sdw_slv_release, +}; + +static struct device_type sdw_mstr_type = { + .groups = NULL, + .release = sdw_mstr_release, +}; + +/** + * sdw_slv_verify - return parameter as sdw_slave, or NULL + * @dev: device, probably from some driver model iterator + * + * When traversing the driver model tree, perhaps using driver model + * iterators like @device_for_each_child(), you can't assume very much + * about the nodes you find. Use this function to avoid oopses caused + * by wrongly treating some non-SDW device as an sdw_slave. + */ +static struct sdw_slave *sdw_slv_verify(struct device *dev) +{ + return (dev->type == &sdw_slv_type) + ? to_sdw_slave(dev) + : NULL; +} + +/** + * sdw_mstr_verify: return parameter as sdw_master, or NULL + * + * @dev: device, probably from some driver model iterator + * + * When traversing the driver model tree, perhaps using driver model + * iterators like @device_for_each_child(), you can't assume very much + * about the nodes you find. Use this function to avoid oopses caused + * by wrongly treating some non-SDW device as an sdw_master. + */ +static struct sdw_master *sdw_mstr_verify(struct device *dev) +{ + return (dev->type == &sdw_mstr_type) + ? to_sdw_master(dev) + : NULL; +} + +static const struct sdw_slave_id *sdw_match_slv(const struct sdw_slave_id *id, + const struct sdw_slave *sdw_slv) +{ + const struct sdw_slave_priv *slv_priv = &sdw_slv->priv; + + if (!id) + return NULL; + + /* + * IDs should be NULL terminated like the last ID in the list should + * be null, as done for drivers like platform, i2c etc. + */ + while (id->name[0]) { + if (strncmp(slv_priv->name, id->name, SOUNDWIRE_NAME_SIZE) == 0) + return id; + + id++; + } + + return NULL; +} + +static const struct sdw_master_id *sdw_match_mstr( + const struct sdw_master_id *id, + const struct sdw_master *sdw_mstr) +{ + if (!id) + return NULL; + + /* + * IDs should be NULL terminated like the last ID in the list should + * be null, as done for drivers like platform, i2c etc. + */ + while (id->name[0]) { + if (strncmp(sdw_mstr->name, id->name, SOUNDWIRE_NAME_SIZE) == 0) + return id; + id++; + } + return NULL; +} + +static int sdw_slv_match(struct device *dev, struct device_driver *driver) +{ + struct sdw_slave *sdw_slv; + struct sdw_driver *sdw_drv = to_sdw_driver(driver); + struct sdw_slave_driver *drv; + int ret = 0; + + + if (sdw_drv->driver_type != SDW_DRIVER_TYPE_SLAVE) + return ret; + + drv = to_sdw_slave_driver(driver); + sdw_slv = to_sdw_slave(dev); + + /* + * We are matching based on the dev_id field, dev_id field is unique + * based on part_id and manufacturer id. Device will be registered + * based on dev_id and driver will also have same dev_id for device + * its controlling. + */ + ret = (sdw_match_slv(drv->id_table, sdw_slv) != NULL); + + if (ret < 0) + sdw_slv->priv.driver = drv; + + return ret; +} + +static int sdw_mstr_match(struct device *dev, struct device_driver *driver) +{ + struct sdw_master *sdw_mstr; + struct sdw_driver *sdw_drv = to_sdw_driver(driver); + struct sdw_master_driver *drv; + int ret = 0; + + if (sdw_drv->driver_type != SDW_DRIVER_TYPE_MASTER) + return ret; + + drv = to_sdw_master_driver(driver); + sdw_mstr = to_sdw_master(dev); + + ret = (sdw_match_mstr(drv->id_table, sdw_mstr) != NULL); + + if (driver->name && !ret) + ret = (strncmp(sdw_mstr->name, driver->name, + SOUNDWIRE_NAME_SIZE) == 0); + + if (ret < 0) + sdw_mstr->driver = drv; + + return ret; +} + +static int sdw_mstr_probe(struct device *dev) +{ + const struct sdw_master_driver *sdrv = + to_sdw_master_driver(dev->driver); + struct sdw_master *mstr = to_sdw_master(dev); + int ret; + + ret = dev_pm_domain_attach(dev, true); + + if (ret != -EPROBE_DEFER) { + ret = sdrv->probe(mstr, sdw_match_mstr(sdrv->id_table, mstr)); + if (ret < 0) + dev_pm_domain_detach(dev, true); + } + + return ret; +} + +static int sdw_slv_probe(struct device *dev) +{ + const struct sdw_slave_driver *sdrv = to_sdw_slave_driver(dev->driver); + struct sdw_slave *sdwslv = to_sdw_slave(dev); + int ret; + + ret = dev_pm_domain_attach(dev, true); + + if (ret != -EPROBE_DEFER) { + ret = sdrv->probe(sdwslv, sdw_match_slv(sdrv->id_table, + sdwslv)); + if (ret < 0) + dev_pm_domain_detach(dev, true); + } + + return ret; +} + +static int sdw_mstr_remove(struct device *dev) +{ + const struct sdw_master_driver *sdrv = + to_sdw_master_driver(dev->driver); + int ret; + + ret = sdrv->remove(to_sdw_master(dev)); + dev_pm_domain_detach(dev, true); + return ret; + +} + +static int sdw_slv_remove(struct device *dev) +{ + const struct sdw_slave_driver *sdrv = to_sdw_slave_driver(dev->driver); + int ret; + + ret = sdrv->remove(to_sdw_slave(dev)); + dev_pm_domain_detach(dev, true); + + return ret; +} + +static void sdw_slv_shutdown(struct device *dev) +{ + const struct sdw_slave_driver *sdrv = + to_sdw_slave_driver(dev->driver); + + sdrv->shutdown(to_sdw_slave(dev)); +} + +static void sdw_mstr_shutdown(struct device *dev) +{ + const struct sdw_master_driver *sdrv = + to_sdw_master_driver(dev->driver); + + sdrv->shutdown(to_sdw_master(dev)); +} + +static int sdw_match(struct device *dev, struct device_driver *driver) +{ + struct sdw_slave *sdw_slv; + struct sdw_master *sdw_mstr; + + sdw_slv = sdw_slv_verify(dev); + if (sdw_slv) + return sdw_slv_match(dev, driver); + + sdw_mstr = sdw_mstr_verify(dev); + if (sdw_mstr) + return sdw_mstr_match(dev, driver); + + /* + * Returning 0 to calling function means match not found, so calling + * function will not call probe + */ + return 0; + +} + +static const struct dev_pm_ops soundwire_pm = { + .suspend = pm_generic_suspend, + .resume = pm_generic_resume, + SET_RUNTIME_PM_OPS( + pm_generic_runtime_suspend, + pm_generic_runtime_resume, + NULL) +}; + +static struct bus_type sdw_bus_type = { + .name = "soundwire", + .match = sdw_match, + .pm = &soundwire_pm, +}; + +/** + * snd_sdw_master_register_driver: SoundWire Master driver registration with + * bus. This API will register the Master driver with the SoundWire + * bus. It is typically called from the driver's module-init function. + * + * @driver: Master Driver to be associated with Master interface. + * @owner: Module owner, generally THIS module. + */ +int snd_sdw_master_register_driver(struct sdw_master_driver *driver, + struct module *owner) +{ + int ret; + + if (!driver->probe) + return -EINVAL; + + if (!driver->ops->xfer_msg || !driver->ops->reset_page_addr) + return -EINVAL; + + if (!driver->port_ops->dpn_set_port_params || + !driver->port_ops->dpn_set_port_transport_params || + !driver->port_ops->dpn_port_enable_ch) + return -EINVAL; + + driver->driver.probe = sdw_mstr_probe; + + if (driver->remove) + driver->driver.remove = sdw_mstr_remove; + if (driver->shutdown) + driver->driver.shutdown = sdw_mstr_shutdown; + + /* add the driver to the list of sdw drivers in the driver core */ + driver->driver.owner = owner; + driver->driver.bus = &sdw_bus_type; + + /* + * When registration returns, the driver core will have called + * probe() for all matching-but-unbound Slaves, devices which are + * not bind to any driver still. + */ + ret = driver_register(&driver->driver); + if (ret) + return ret; + + pr_debug("sdw-core: driver [%s] registered\n", driver->driver.name); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sdw_master_register_driver); + +/** + * snd_sdw_slave_driver_register: SoundWire Slave driver registration with + * bus. This API will register the Slave driver with the SoundWire bus. + * It is typically called from the driver's module-init function. + * + * @driver: Driver to be associated with Slave. + * @owner: Module owner, generally THIS module. + */ +int snd_sdw_slave_driver_register(struct sdw_slave_driver *driver, + struct module *owner) +{ + int ret; + + if (driver->probe) + driver->driver.probe = sdw_slv_probe; + if (driver->remove) + driver->driver.remove = sdw_slv_remove; + if (driver->shutdown) + driver->driver.shutdown = sdw_slv_shutdown; + + /* Add the driver to the list of sdw drivers in the driver core */ + driver->driver.owner = owner; + driver->driver.bus = &sdw_bus_type; + + /* + * When registration returns, the driver core will have called + * probe() for all matching-but-unbound Slaves. + */ + ret = driver_register(&driver->driver); + if (ret) + return ret; + + pr_debug("sdw-core: driver [%s] registered\n", driver->driver.name); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sdw_slave_driver_register); + +static int sdw_copy_aud_mod_prop(struct sdw_port_aud_mode_prop *slv_prop, + struct sdw_port_aud_mode_prop *prop) +{ + /* + * Currently goto is used in API to perform different + * operations. TODO: Avoid usage of goto statement + */ + memcpy(slv_prop, prop, sizeof(*prop)); + + if (!prop->num_bus_freq_cfgs) + goto handle_sample_rate; + + slv_prop->clk_freq_buf = kcalloc(prop->num_bus_freq_cfgs, + sizeof(unsigned int), + GFP_KERNEL); + + if (!slv_prop->clk_freq_buf) + goto mem_error; + + memcpy(slv_prop->clk_freq_buf, prop->clk_freq_buf, + (prop->num_bus_freq_cfgs * + sizeof(unsigned int))); + +handle_sample_rate: + + if (!prop->num_sample_rate_cfgs) + return 0; + + slv_prop->sample_rate_buf = kcalloc(prop->num_sample_rate_cfgs, + sizeof(unsigned int), + GFP_KERNEL); + + if (!slv_prop->sample_rate_buf) + goto mem_error; + + memcpy(slv_prop->sample_rate_buf, prop->sample_rate_buf, + (prop->num_sample_rate_cfgs * + sizeof(unsigned int))); + + return 0; + +mem_error: + kfree(prop->clk_freq_buf); + kfree(slv_prop->sample_rate_buf); + return -ENOMEM; + +} + +static int sdw_update_dpn_caps(struct sdw_dpn_caps *slv_dpn_cap, + struct sdw_dpn_caps *dpn_cap) +{ + int j, ret = 0; + struct sdw_port_aud_mode_prop *slv_prop, *prop; + + /* + * Currently goto is used in API to perform different + * operations. TODO: Avoid usage of goto statement + */ + + /* + * slv_prop and prop are using to make copy of mode properties. + * prop holds mode properties received which needs to be updated to + * slv_prop. + */ + + memcpy(slv_dpn_cap, dpn_cap, sizeof(*dpn_cap)); + + /* + * Copy bps (bits per sample) buffer as part of Slave capabilities + */ + if (!dpn_cap->num_bps) + goto handle_ch_cnt; + + slv_dpn_cap->bps_buf = kcalloc(dpn_cap->num_bps, sizeof(u8), + GFP_KERNEL); + + if (!slv_dpn_cap->bps_buf) { + ret = -ENOMEM; + goto error; + } + + memcpy(slv_dpn_cap->bps_buf, dpn_cap->bps_buf, + (dpn_cap->num_bps * sizeof(u8))); + +handle_ch_cnt: + if (!dpn_cap->num_ch_cnt) + goto handle_audio_mode_prop; + + slv_dpn_cap->ch_cnt_buf = kcalloc(dpn_cap->num_ch_cnt, sizeof(u8), + GFP_KERNEL); + if (!dpn_cap->num_ch_cnt) { + ret = -ENOMEM; + goto error; + } + + /* Copy channel count buffer as part of Slave capabilities */ + memcpy(slv_dpn_cap->ch_cnt_buf, dpn_cap->ch_cnt_buf, + (dpn_cap->num_ch_cnt * sizeof(u8))); + +handle_audio_mode_prop: + + slv_dpn_cap->mode_properties = kzalloc((sizeof(*slv_prop) * + dpn_cap->num_audio_modes), + GFP_KERNEL); + + if (!slv_dpn_cap->mode_properties) { + ret = -ENOMEM; + goto error; + } + + for (j = 0; j < dpn_cap->num_audio_modes; j++) { + + prop = &dpn_cap->mode_properties[j]; + slv_prop = &slv_dpn_cap->mode_properties[j]; + + /* Copy audio properties as part of Slave capabilities */ + ret = sdw_copy_aud_mod_prop(slv_prop, prop); + if (ret < 0) + goto error; + } + + return ret; + +error: + kfree(slv_dpn_cap->mode_properties); + kfree(slv_dpn_cap->ch_cnt_buf); + kfree(slv_dpn_cap->bps_buf); + return ret; + +} + +/* Free all the memory allocated for registering the capabilities */ +static void sdw_unregister_slv_caps(struct sdw_slave *sdw, + unsigned int num_port_direction) +{ + int i, j, k; + struct sdw_slave_caps *caps = &sdw->priv.caps; + struct sdw_dpn_caps *dpn_cap; + struct sdw_port_aud_mode_prop *mode_prop; + u8 ports; + + for (i = 0; i < num_port_direction; i++) { + + if (i == SDW_DATA_DIR_OUT) + ports = caps->num_src_ports; + else + ports = caps->num_sink_ports; + for (j = 0; j < ports; j++) { + dpn_cap = &caps->dpn_caps[i][j]; + kfree(dpn_cap->bps_buf); + kfree(dpn_cap->ch_cnt_buf); + + for (k = 0; k < dpn_cap->num_audio_modes; k++) { + mode_prop = dpn_cap->mode_properties; + kfree(mode_prop->clk_freq_buf); + kfree(mode_prop->sample_rate_buf); + } + } + } +} + +static inline void sdw_copy_slv_caps(struct sdw_slave *sdw, + struct sdw_slave_caps *caps) +{ + struct sdw_slave_caps *slv_caps; + + slv_caps = &sdw->priv.caps; + + memcpy(slv_caps, caps, sizeof(*slv_caps)); +} + +/** + * snd_sdw_slave_register_caps: Register Slave device capabilities to the + * bus driver. Since bus driver handles bunch of Slave register + * programming it should be aware of Slave device capabilities. Slave + * device is attached to bus based on enumeration. Once Slave driver is + * attached to device and probe of Slave driver is called on device and + * driver binding, Slave driver should call this function to register + * its capabilities to bus. This should be the very first function to + * bus driver from Slave driver once Slave driver is registered and + * probed. + * + * @slave: SoundWire Slave handle. + * @cap: Slave caps to be registered to bus driver. + */ +int snd_sdw_slave_register_caps(struct sdw_slave *slave, + struct sdw_slave_caps *cap) +{ + struct sdw_slave_caps *caps; + struct sdw_dpn_caps *slv_dpn_cap, *dpn_cap; + int i, j, ret; + u8 ports; + + caps = &slave->priv.caps; + + sdw_copy_slv_caps(slave, cap); + + for (i = 0; i < SDW_MAX_PORT_DIRECTIONS; i++) { + if (i == SDW_DATA_DIR_OUT) + ports = caps->num_src_ports; + else + ports = caps->num_sink_ports; + + caps->dpn_caps[i] = kzalloc((sizeof(*slv_dpn_cap) * + ports), GFP_KERNEL); + + if (caps->dpn_caps[i] == NULL) { + ret = -ENOMEM; + goto error; + } + } + + for (i = 0; i < SDW_MAX_PORT_DIRECTIONS; i++) { + + if (i == SDW_DATA_DIR_OUT) + ports = caps->num_src_ports; + else + ports = caps->num_sink_ports; + + for (j = 0; j < ports; j++) { + + dpn_cap = &cap->dpn_caps[i][j]; + slv_dpn_cap = &caps->dpn_caps[i][j]; + + ret = sdw_update_dpn_caps(&caps->dpn_caps[i][j], + &cap->dpn_caps[i][j]); + if (ret < 0) { + dev_err(&slave->mstr->dev, "Failed to update Slave caps ret = %d\n", ret); + goto error; + } + } + } + + slave->priv.slave_cap_updated = true; + + return 0; + +error: + sdw_unregister_slv_caps(slave, i); + return ret; +} +EXPORT_SYMBOL_GPL(snd_sdw_slave_register_caps); + +/** + * snd_sdw_master_add: Registers the SoundWire Master interface. This needs + * to be called for each Master interface supported by SoC. This + * represents One clock and data line (Optionally multiple data lanes) + * of Master interface. + * + * @master: the Master to be added. + */ +int snd_sdw_master_add(struct sdw_master *master) +{ + int i, id, ret; + struct sdw_bus *sdw_bus = NULL; + + /* Sanity checks */ + if (unlikely(master->name[0] == '\0')) { + pr_err("sdw-core: Attempt to register a master with no name!\n"); + return -EINVAL; + } + + mutex_lock(&snd_sdw_core.core_mutex); + + /* Always start bus with 0th Index */ + id = idr_alloc(&snd_sdw_core.idr, master, 0, 0, GFP_KERNEL); + + if (id < 0) { + mutex_unlock(&snd_sdw_core.core_mutex); + return id; + } + + master->nr = id; + + /* + * Initialize the DeviceNumber in the Master structure. Each of + * these is assigned to the Slaves enumerating on this Master + * interface. + */ + for (i = 0; i <= SDW_MAX_DEVICES; i++) + master->sdw_addr[i].dev_num = i; + + mutex_init(&master->lock); + mutex_init(&master->msg_lock); + INIT_LIST_HEAD(&master->slv_list); + INIT_LIST_HEAD(&master->mstr_rt_list); + + sdw_bus = kzalloc(sizeof(*sdw_bus), GFP_KERNEL); + if (!sdw_bus) { + ret = -ENOMEM; + goto alloc_failed; + } + + sdw_bus->mstr = master; + master->bus = sdw_bus; + + dev_set_name(&master->dev, "sdw-%d", master->nr); + master->dev.bus = &sdw_bus_type; + master->dev.type = &sdw_mstr_type; + + ret = device_register(&master->dev); + if (ret < 0) + goto dev_reg_failed; + + dev_dbg(&master->dev, "master [%s] registered\n", master->name); + + /* + * Add bus to the list of buses inside core. This is list of Slave + * devices enumerated on this bus. Adding new devices at end. It can + * be added at any location in list. + */ + list_add_tail(&sdw_bus->bus_node, &snd_sdw_core.bus_list); + mutex_unlock(&snd_sdw_core.core_mutex); + + return 0; + +dev_reg_failed: + kfree(sdw_bus); +alloc_failed: + idr_remove(&snd_sdw_core.idr, master->nr); + mutex_unlock(&snd_sdw_core.core_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_sdw_master_add); + +static void sdw_unregister_slv(struct sdw_slave *sdw_slv) +{ + struct sdw_master *mstr; + + mstr = sdw_slave_to_master(sdw_slv); + + sdw_unregister_slv_caps(sdw_slv, SDW_MAX_PORT_DIRECTIONS); + + mutex_lock(&mstr->lock); + list_del(&sdw_slv->priv.node); + mutex_unlock(&mstr->lock); + + mstr->sdw_addr[sdw_slv->dev_num].assigned = false; + + device_unregister(&sdw_slv->dev); + kfree(sdw_slv); +} + +static int __unregister_slv(struct device *dev, void *dummy) +{ + struct sdw_slave *slave = sdw_slv_verify(dev); + + if (slave) + sdw_unregister_slv(slave); + + return 0; +} + +/** + * snd_sdw_master_del - unregister SDW Master + * + * @master: the Master being unregistered + */ +void snd_sdw_master_del(struct sdw_master *master) +{ + struct sdw_master *found; + + /* First make sure that this Master was ever added */ + mutex_lock(&snd_sdw_core.core_mutex); + found = idr_find(&snd_sdw_core.idr, master->nr); + + if (found != master) { + pr_debug("sdw-core: attempting to delete unregistered master [%s]\n", + master->name); + mutex_unlock(&snd_sdw_core.core_mutex); + return; + } + /* + * Detach any active Slaves. This can't fail, thus we do not check + * the returned value. + */ + device_for_each_child(&master->dev, NULL, __unregister_slv); + + /* device name is gone after device_unregister */ + dev_dbg(&master->dev, "master [%s] unregistered\n", master->name); + + /* wait until all references to the device are gone */ + init_completion(&master->slv_released_complete); + device_unregister(&master->dev); + wait_for_completion(&master->slv_released_complete); + + /* free bus id */ + idr_remove(&snd_sdw_core.idr, master->nr); + mutex_unlock(&snd_sdw_core.core_mutex); + + /* + * Clear the device structure in case this Master is ever going to + * be added again + */ + memset(&master->dev, 0, sizeof(master->dev)); +} +EXPORT_SYMBOL_GPL(snd_sdw_master_del); + +/** + * snd_sdw_master_get: Return the Master handle from Master number. + * Increments the reference count of the module. Similar to + * i2c_get_adapter. + * + * @nr: Master number. + * + * Returns Master handle on success, else NULL + */ +struct sdw_master *snd_sdw_master_get(int nr) +{ + struct sdw_master *master; + + mutex_lock(&snd_sdw_core.core_mutex); + + master = idr_find(&snd_sdw_core.idr, nr); + if (master && !try_module_get(master->driver->driver.owner)) + master = NULL; + + mutex_unlock(&snd_sdw_core.core_mutex); + + return master; +} +EXPORT_SYMBOL_GPL(snd_sdw_master_get); + +/** + * snd_sdw_master_put: Reverses the effect of sdw_master_get + * + * @master: Master handle. + */ +void snd_sdw_master_put(struct sdw_master *master) +{ + if (master) + module_put(master->driver->driver.owner); +} +EXPORT_SYMBOL_GPL(snd_sdw_master_put); + +static void sdw_exit(void) +{ + bus_unregister(&sdw_bus_type); +} + +static int sdw_init(void) +{ + int retval; + + mutex_init(&snd_sdw_core.core_mutex); + INIT_LIST_HEAD(&snd_sdw_core.bus_list); + idr_init(&snd_sdw_core.idr); + retval = bus_register(&sdw_bus_type); + + if (retval) + bus_unregister(&sdw_bus_type); + return retval; +} + +subsys_initcall(sdw_init); +module_exit(sdw_exit); + +MODULE_AUTHOR("Hardik Shah hardik.t.shah@intel.com"); +MODULE_AUTHOR("Sanyog Kale sanyog.r.kale@intel.com"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("SoundWire bus driver"); +MODULE_ALIAS("platform:soundwire"); diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h new file mode 100644 index 0000000..5911aa6 --- /dev/null +++ b/sound/sdw/sdw_priv.h @@ -0,0 +1,102 @@ +/* + * sdw_priv.h - Private definition for SoundWire bus interface. + * + * Author: Hardik Shah hardik.t.shah@intel.com + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * + * BSD LICENSE + * + * Copyright(c) 2016 Intel Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef _LINUX_SDW_PRIV_H +#define _LINUX_SDW_PRIV_H + +/** + * sdw_driver: Structure to typecast both Master and Slave driver to generic + * SoundWire driver, to find out the driver type. + * + * @driver_type: Type of SoundWire driver, Master or Slave. + * @driver: Generic Linux driver. + */ +struct sdw_driver { + enum sdw_driver_type driver_type; + struct device_driver driver; +}; +#define to_sdw_driver(d) \ + container_of(d, struct sdw_driver, driver) +/** + * sdw_bus: Bus structure holding bus related information. + * + * @bus_node: Node to add the bus in the sdw_core list. + * @mstr: Master reference for the bus. + */ + +struct sdw_bus { + struct list_head bus_node; + struct sdw_master *mstr; +}; + +/** + * snd_sdw_core: Global SoundWire structure. It handles all the streams + * spawned across masters and has list of bus structure per every + * Master registered. + * + * @bus_list: List of all the bus instance. + * @core_mutex: Global lock for all bus instances. + * @idr: For identifying the registered buses. + */ +struct snd_sdw_core { + struct list_head bus_list; + struct mutex core_mutex; + struct idr idr; +}; + +#endif /* _LINUX_SDW_PRIV_H */
On Fri, Oct 21, 2016 at 06:11:04PM +0530, Hardik Shah wrote:
+static void sdw_mstr_release(struct device *dev) +{
- struct sdw_master *mstr = to_sdw_master(dev);
- complete(&mstr->slv_released_complete);
+}
Other buses don't do this... this is a big warning sign that you're abusing the driver model.
+/**
- sdw_slv_verify - return parameter as sdw_slave, or NULL
- @dev: device, probably from some driver model iterator
- When traversing the driver model tree, perhaps using driver model
- iterators like @device_for_each_child(), you can't assume very much
- about the nodes you find. Use this function to avoid oopses caused
- by wrongly treating some non-SDW device as an sdw_slave.
- */
This is also *very* scary, especially given that there's no analysis presented as to why there might be random other things on the bus. Why does SoundWire need this when other buses don't?
+static struct sdw_slave *sdw_slv_verify(struct device *dev) +{
- return (dev->type == &sdw_slv_type)
? to_sdw_slave(dev)
: NULL;
This is needlessly obfuscated, if you want to write an if statement write an if statement.
+static int sdw_slv_match(struct device *dev, struct device_driver *driver) +{
- struct sdw_slave *sdw_slv;
- struct sdw_driver *sdw_drv = to_sdw_driver(driver);
- struct sdw_slave_driver *drv;
- int ret = 0;
- if (sdw_drv->driver_type != SDW_DRIVER_TYPE_SLAVE)
return ret;
Why do we need a check like this?
+static int sdw_mstr_probe(struct device *dev) +{
- const struct sdw_master_driver *sdrv =
to_sdw_master_driver(dev->driver);
- struct sdw_master *mstr = to_sdw_master(dev);
- int ret;
- ret = dev_pm_domain_attach(dev, true);
- if (ret != -EPROBE_DEFER) {
ret = sdrv->probe(mstr, sdw_match_mstr(sdrv->id_table, mstr));
if (ret < 0)
dev_pm_domain_detach(dev, true);
- }
This looks *very* broken. Surely if we fail to attach a pm_domain for any reason other than one not being there to attach we shouldn't be trying to probe the device?
+EXPORT_SYMBOL_GPL(snd_sdw_master_register_driver);
This is EXPORT_SYMBOL_GPL() but the bus itself is dual licensed GPL/BSD - seems a bit inconsistent.
+/**
- snd_sdw_master_add: Registers the SoundWire Master interface. This needs
- to be called for each Master interface supported by SoC. This
- represents One clock and data line (Optionally multiple data lanes)
- of Master interface.
- @master: the Master to be added.
- */
+int snd_sdw_master_add(struct sdw_master *master)
This lies at the heart of the issues that seem to exist with the misuse of the driver model in this code. Normally what we see is that the controller would instantiate as whatever bus type the controller is attached by (typically a PCI or platform device) and then it wouild register a bus with the bus subsystem which would then instantiate slaves. Instead we have this system where the bus is registered by something in the system and then the master is a driver on the bus parallel to the slaves but with a separate driver type that causes confusion. Without having seen a master driver it's not even clear how this is going to work and allow the master to talk to its own hardware.
On Mon, Nov 14, 2016 at 01:37:51PM +0000, Mark Brown wrote:
On Fri, Oct 21, 2016 at 06:11:04PM +0530, Hardik Shah wrote:
+static void sdw_mstr_release(struct device *dev) +{
- struct sdw_master *mstr = to_sdw_master(dev);
- complete(&mstr->slv_released_complete);
+}
Other buses don't do this... this is a big warning sign that you're abusing the driver model.
The whole master enumeration stuff, as we discussed in LPC will go away now. So it will be more like other buses :)
+/**
- sdw_slv_verify - return parameter as sdw_slave, or NULL
- @dev: device, probably from some driver model iterator
- When traversing the driver model tree, perhaps using driver model
- iterators like @device_for_each_child(), you can't assume very much
- about the nodes you find. Use this function to avoid oopses caused
- by wrongly treating some non-SDW device as an sdw_slave.
- */
This is also *very* scary, especially given that there's no analysis presented as to why there might be random other things on the bus. Why does SoundWire need this when other buses don't?
Sure I will double check on this one and sounds to me we cna remove this..
+static struct sdw_slave *sdw_slv_verify(struct device *dev) +{
- return (dev->type == &sdw_slv_type)
? to_sdw_slave(dev)
: NULL;
This is needlessly obfuscated, if you want to write an if statement write an if statement.
Sure thing
+static int sdw_slv_match(struct device *dev, struct device_driver *driver) +{
- struct sdw_slave *sdw_slv;
- struct sdw_driver *sdw_drv = to_sdw_driver(driver);
- struct sdw_slave_driver *drv;
- int ret = 0;
- if (sdw_drv->driver_type != SDW_DRIVER_TYPE_SLAVE)
return ret;
Why do we need a check like this?
Since folks were doing both slave and master matches, this was done to be double sure, but this will go away now.
+static int sdw_mstr_probe(struct device *dev) +{
- const struct sdw_master_driver *sdrv =
to_sdw_master_driver(dev->driver);
- struct sdw_master *mstr = to_sdw_master(dev);
- int ret;
- ret = dev_pm_domain_attach(dev, true);
- if (ret != -EPROBE_DEFER) {
ret = sdrv->probe(mstr, sdw_match_mstr(sdrv->id_table, mstr));
if (ret < 0)
dev_pm_domain_detach(dev, true);
- }
This looks *very* broken. Surely if we fail to attach a pm_domain for any reason other than one not being there to attach we shouldn't be trying to probe the device?
Yes I agree, we shouldnt be doing probing in that case. Will fix that up.
+EXPORT_SYMBOL_GPL(snd_sdw_master_register_driver);
This is EXPORT_SYMBOL_GPL() but the bus itself is dual licensed GPL/BSD
- seems a bit inconsistent.
Thanks for pointing this out. The symbols should use EXPORT_SYMBOL() only and the ones which link to other kernel GPL ones would need to be GPL ones.
+/**
- snd_sdw_master_add: Registers the SoundWire Master interface. This needs
- to be called for each Master interface supported by SoC. This
- represents One clock and data line (Optionally multiple data lanes)
- of Master interface.
- @master: the Master to be added.
- */
+int snd_sdw_master_add(struct sdw_master *master)
This lies at the heart of the issues that seem to exist with the misuse of the driver model in this code. Normally what we see is that the controller would instantiate as whatever bus type the controller is attached by (typically a PCI or platform device) and then it wouild register a bus with the bus subsystem which would then instantiate slaves. Instead we have this system where the bus is registered by something in the system and then the master is a driver on the bus parallel to the slaves but with a separate driver type that causes confusion. Without having seen a master driver it's not even clear how this is going to work and allow the master to talk to its own hardware.
Yes as discussed the whole master stuff will eb redone so you wont see these bits in next rev.
Add register definitions for the SoundWire Slave. This is as per MIPI SoundWire spec 1.1.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sdw/sdw_registers.h | 277 +++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 include/sound/sdw/sdw_registers.h
diff --git a/include/sound/sdw/sdw_registers.h b/include/sound/sdw/sdw_registers.h new file mode 100644 index 0000000..48ecdf5 --- /dev/null +++ b/include/sound/sdw/sdw_registers.h @@ -0,0 +1,277 @@ +/* + * sdw_registers.h - SoundWire MIPI spec 1.1 defined SoundWire Slave + * register definition file. This defines the register offsets, bit + * mask and bit shift in accordance with MIPI spec 1.1. + * + * Author: Hardik Shah hardik.t.shah@intel.com + * + * + * This header file refers to the MIPI SoundWire 1.1 Spec. + * [1.1] https://members.mipi.org/wg/All-Members/document (accessible to + * MIPI members). + * + * The comments in the file try to follow the same conventions with a + * capital letter for all standard definitions such as Master, Slave, + * Data Port, etc. When possible, the constant numeric values are kept + * the same as in the MIPI specifications. All of the constants reflect + * the MIPI SoundWire definitions and are not vendor specific. Some of + * the constants are for bus driver usage and not MIPI defined. These + * are specified at appropriate place in this file. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * + * BSD LICENSE + * + * Copyright(c) 2016 Intel Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#ifndef _LINUX_SDW_REG_H +#define _LINUX_SDW_REG_H + +#define SDW_SCP_ADDRPAGE1_MASK 0xFF +#define SDW_SCP_ADDRPAGE1_SHIFT 15 + +#define SDW_SCP_ADDRPAGE2_MASK 0xFF +#define SDW_SCP_ADDRPAGE2_SHIFT 22 + +#define SDW_REGADDR_SHIFT 0x0 +#define SDW_REGADDR_MASK 0xFFFF + +#define SDW_MAX_REG_ADDR 65536 + + +#define SDW_NUM_DATA_PORT_REGISTERS 0x100 +#define SDW_BANK1_REGISTER_OFFSET 0x10 + +#define SDW_DP0_INTSTAT 0x0 +#define SDW_DP0_INTSTAT_TEST_FAIL_MASK BIT_MASK(0) +#define SDW_DP0_INTSTAT_PORT_READY_MASK BIT_MASK(1) +#define SDW_DP0_INTSTAT_BRA_FAILURE_MASK BIT_MASK(2) +#define SDW_DP0_INTSTAT_IMPDEF1_MASK BIT_MASK(5) +#define SDW_DP0_INTSTAT_IMPDEF2_MASK BIT_MASK(6) +#define SDW_DP0_INTSTAT_IMPDEF3_MASK BIT_MASK(7) + +#define SDW_DP0_INTCLEAR 0x0 +#define SDW_DP0_INTCLEAR_TEST_FAIL_MASK BIT_MASK(0) +#define SDW_DP0_INTCLEAR_PORT_READY_MASK BIT_MASK(1) +#define SDW_DP0_INTCLEAR_BRA_FAILURE_MASK BIT_MASK(2) +#define SDW_DP0_INTCLEAR_IMPDEF1_MASK BIT_MASK(5) +#define SDW_DP0_INTCLEAR_IMPDEF2_MASK BIT_MASK(6) +#define SDW_DP0_INTCLEAR_IMPDEF3_MASK BIT_MASK(7) + +#define SDW_DP0_INTMASK 0x1 +#define SDW_DP0_INTMASK_TEST_FAIL_MASK BIT_MASK(0) +#define SDW_DP0_INTMASK_PORT_READY_MASK BIT_MASK(1) +#define SDW_DP0_INTMASK_BRA_FAILURE_MASK BIT_MASK(2) +#define SDW_DP0_INTMASK_IMPDEF1_MASK BIT_MASK(5) +#define SDW_DP0_INTMASK_IMPDEF2_MASK BIT_MASK(6) +#define SDW_DP0_INTMASK_IMPDEF3_MASK BIT_MASK(7) + +#define SDW_DP0_PORTCTRL 0x2 +#define SDW_DP0_PORTCTRL_PORTDATAMODE_MASK GENMASK(3, 2) +#define SDW_DP0_PORTCTRL_PORTDATAMODE_SHIFT 2 +#define SDW_DP0_PORTCTRL_NEXTINVERTBANK_MASK BIT_MASK(4) +#define SDW_DP0_PORTCTRL_NEXTINVERTBANK_SHIFT 4 +#define SDW_DP0_PORTCTRL_BPT_PAYLD_TYPE_MASK GENMASK(7, 6) +#define SDW_DP0_PORTCTRL_BPT_PAYLD_TYPE_SHIFT 6 + + +#define SDW_DP0_BLOCKCTRL1 0x3 + +#define SDW_DP0_PREPARESTATUS 0x4 + +#define SDW_DP0_PREPARECTRL 0x5 + +#define SDW_DP0_CHANNELEN 0x20 +#define SDW_DP0_SAMPLECTRL1 0x22 +#define SDW_DP0_SAMPLECTRL2 0x23 +#define SDW_DP0_OFFSETCTRL1 0x24 +#define SDW_DP0_OFFSETCTRL2 0x25 +#define SDW_DP0_HCTRL 0x26 +#define SDW_DP0_LANECTRL 0x28 + +#define SDW_SCP_INTSTAT1 0x40 +#define SDW_SCP_INTSTAT1_PARITY_MASK BIT_MASK(0) +#define SDW_SCP_INTSTAT1_BUS_CLASH_MASK BIT_MASK(1) +#define SDW_SCP_INTSTAT1_IMPL_DEF_MASK BIT_MASK(2) +#define SDW_SCP_INTSTAT1_SCP2_CASCADE_MASK BIT_MASK(7) + +#define SDW_SCP_INTCLEAR1 0x40 +#define SDW_SCP_INTCLEAR1_PARITY_MASK BIT_MASK(0) +#define SDW_SCP_INTCLEAR1_BUS_CLASH_MASK BIT_MASK(1) +#define SDW_SCP_INTCLEAR1_IMPL_DEF_MASK BIT_MASK(2) + +#define SDW_SCP_INTMASK1 0x41 +#define SDW_SCP_INTMASK1_PARITY_MASK BIT_MASK(0) +#define SDW_SCP_INTMASK1_BUS_CLASH_MASK BIT_MASK(1) +#define SDW_SCP_INTMASK1_IMPL_DEF_MASK BIT_MASK(2) + + +#define SDW_SCP_INTSTAT2 0x42 +#define SDW_SCP_INTSTAT2_SCP3_CASCADE_MASK BIT_MASK(7) + + +#define SDW_SCP_INTSTAT3 0x43 + +/* Number of interrupt status registers */ +#define SDW_NUM_INT_STAT_REGISTERS 3 + +/* Number of interrupt clear registers */ +#define SDW_NUM_INT_CLEAR_REGISTERS 1 + +#define SDW_SCP_CTRL 0x44 +#define SDW_SCP_CTRL_CLK_STP_NOW_MASK BIT_MASK(1) +#define SDW_SCP_CTRL_FORCE_RESET_MASK BIT_MASK(2) +#define SDW_SCP_CTRL_CLK_STP_NOW_SHIFT 0x1 + +#define SDW_SCP_STAT 0x44 +#define SDW_SCP_STAT_CLK_STP_NF_MASK BIT_MASK(0) + +#define SDW_SCP_SYSTEMCTRL 0x45 +#define SDW_SCP_SYSTEMCTRL_CLK_STP_PREP_MASK BIT_MASK(0) +#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE_MASK BIT_MASK(2) +#define SDW_SCP_SYSTEMCTRL_WAKE_UP_EN_MASK BIT_MASK(3) +#define SDW_SCP_SYSTEMCTRL_HIGH_PHY_MASK BIT_MASK(4) + +#define SDW_SCP_SYSTEMCTRL_CLK_STP_PREP_SHIFT 0x0 +#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE_SHIFT 0x2 +#define SDW_SCP_SYSTEMCTRL_WAKE_UP_EN_SHIFT 0x3 +#define SDW_SCP_SYSTEMCTRL_HIGH_PHY_SHIFT 0x4 + +#define SDW_SCP_DEVNUMBER 0x46 +#define SDW_SCP_HIGH_PHY_CHECK 0x47 +#define SDW_SCP_ADDRPAGE1 0x48 +#define SDW_SCP_ADDRPAGE2 0x49 +#define SDW_SCP_KEEPEREN 0x4A +#define SDW_SCP_BANKDELAY 0x4B +#define SDW_SCP_TESTMODE 0x4F +#define SDW_SCP_DEVID_0 0x50 +#define SDW_SCP_DEVID_1 0x51 +#define SDW_SCP_DEVID_2 0x52 +#define SDW_SCP_DEVID_3 0x53 +#define SDW_SCP_DEVID_4 0x54 +#define SDW_SCP_DEVID_5 0x55 + +/* Banked Registers */ +#define SDW_SCP_FRAMECTRL 0x60 +#define SDW_SCP_NEXTFRAME 0x61 + +#define SDW_DPN_INTSTAT 0x0 +#define SDW_DPN_INTSTAT_TEST_FAIL_MASK BIT_MASK(0) +#define SDW_DPN_INTSTAT_PORT_READY_MASK BIT_MASK(1) +#define SDW_DPN_INTSTAT_IMPDEF1_MASK BIT_MASK(5) +#define SDW_DPN_INTSTAT_IMPDEF2_MASK BIT_MASK(6) +#define SDW_DPN_INTSTAT_IMPDEF3_MASK BIT_MASK(7) + +#define SDW_DPN_INTCLEAR 0x0 +#define SDW_DPN_INTCLEAR_TEST_FAIL_MASK BIT_MASK(0) +#define SDW_DPN_INTCLEAR_PORT_READY_MASK BIT_MASK(1) +#define SDW_DPN_INTCLEAR_IMPDEF1_MASK BIT_MASK(5) +#define SDW_DPN_INTCLEAR_IMPDEF2_MASK BIT_MASK(6) +#define SDW_DPN_INTCLEAR_IMPDEF3_MASK BIT_MASK(7) + +#define SDW_DPN_INTMASK 0x1 +#define SDW_DPN_INTMASK_TEST_FAIL_MASK BIT_MASK(0) +#define SDW_DPN_INTMASK_PORT_READY_MASK BIT_MASK(1) +#define SDW_DPN_INTMASK_IMPDEF1_MASK BIT_MASK(5) +#define SDW_DPN_INTMASK_IMPDEF2_MASK BIT_MASK(6) +#define SDW_DPN_INTMASK_IMPDEF3_MASK BIT_MASK(7) + +#define SDW_DPN_PORTCTRL 0x2 +#define SDW_DPN_PORTCTRL_PORTFLOWMODE_MASK GENMASK(1, 0) +#define SDW_DPN_PORTCTRL_PORTFLOWMODE_SHIFT 0 +#define SDW_DPN_PORTCTRL_PORTDATAMODE_MASK GENMASK(3, 2) +#define SDW_DPN_PORTCTRL_PORTDATAMODE_SHIFT 2 +#define SDW_DPN_PORTCTRL_NEXTINVERTBANK_MASK BIT_MASK(4) +#define SDW_DPN_PORTCTRL_NEXTINVERTBANK_SHIFT 4 + +#define SDW_DPN_BLOCKCTRL1 0x3 +#define SDW_DPN_BLOCKCTRL1_WORDLENGTH_MASK GENMASK(5, 0) +#define SDW_DPN_BLOCKCTRL1_WORDLENGTH_SHIFT 0 + +#define SDW_DPN_PREPARESTATUS 0x4 +#define SDW_DPN_PREPARECTRL 0x5 +#define SDW_DPN_PREPARECTRL_CH_PREPARE_MASK GENMASK(7, 0) + +#define SDW_DPN_CHANNELEN 0x20 +#define SDW_DPN_BLOCKCTRL2 0x21 +#define SDW_DPN_SAMPLECTRL1 0x22 + +#define SDW_DPN_SAMPLECTRL1_LOW_MASK GENMASK(7, 0) +#define SDW_DPN_SAMPLECTRL2 0x23 +#define SDW_DPN_SAMPLECTRL2_SHIFT 8 +#define SDW_DPN_SAMPLECTRL2_LOW_MASK GENMASK(7, 0) +#define SDW_DPN_OFFSETCTRL1 0x24 +#define SDW_DPN_OFFSETCTRL2 0x25 +#define SDW_DPN_HCTRL 0x26 +#define SDW_DPN_HCTRL_HSTART_MASK GENMASK(7, 4) +#define SDW_DPN_HCTRL_HSTOP_MASK GENMASK(3, 0) +#define SDW_DPN_HCTRL_HSTART_SHIFT 4 +#define SDW_DPN_HCTRL_HSTOP_SHIFT 0 +#define SDW_DPN_BLOCKCTRL3 0x27 +#define SDW_DPN_LANECTRL 0x28 + + +#define SDW_NUM_CASC_PORT_INTSTAT1 4 +#define SDW_CASC_PORT_START_INTSTAT1 0 +#define SDW_CASC_PORT_MASK_INTSTAT1 0x8 +#define SDW_CASC_PORT_REG_OFFSET_INTSTAT1 0x0 + +#define SDW_NUM_CASC_PORT_INTSTAT2 7 +#define SDW_CASC_PORT_START_INTSTAT2 4 +#define SDW_CASC_PORT_MASK_INTSTAT2 1 +#define SDW_CASC_PORT_REG_OFFSET_INTSTAT2 1 + +#define SDW_NUM_CASC_PORT_INTSTAT3 4 +#define SDW_CASC_PORT_START_INTSTAT3 11 +#define SDW_CASC_PORT_MASK_INTSTAT3 1 +#define SDW_CASC_PORT_REG_OFFSET_INTSTAT3 2 + + +#endif
This API is used by: 1. Bus driver to read/write MIPI defined registers. 2. Slave driver to read/write SoundWire Slave implementation defined registers. Slave driver should use regmap driver to access implementation defined registers, regmap driver uses read/write APIs internally.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/sdw/sdw.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++++- sound/sdw/sdw_priv.h | 144 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 329 insertions(+), 3 deletions(-)
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c index d4e79b8a..c98e4d7 100644 --- a/sound/sdw/sdw.c +++ b/sound/sdw/sdw.c @@ -335,9 +335,191 @@ static int sdw_match(struct device *dev, struct device_driver *driver) };
/** - * snd_sdw_master_register_driver: SoundWire Master driver registration with - * bus. This API will register the Master driver with the SoundWire - * bus. It is typically called from the driver's module-init function. + * sdw_transfer: Local function where logic is placed to handle NOPM and PM + * variants of the Slave transfer functions. + * + * @mstr: Handle to SDW Master + * @msg: One or more messages to be transferred + * @num: Number of messages to be transferred. + * + * Returns negative error, else the number of messages transferred. + * + */ +static int sdw_transfer(struct sdw_master *mstr, struct sdw_msg *msg, int num, + struct sdw_deferred_xfer_data *data) +{ + unsigned long orig_jiffies; + int ret, try, i; + int program_scp_addr_page = false; + u8 prev_adr_pg1 = 0; + u8 prev_adr_pg2 = 0; + + for (i = 0; i < num; i++) { + + /* Reset timeout for every message */ + orig_jiffies = jiffies; + + /* Inform Master driver to program SCP addr or not */ + if ((prev_adr_pg1 != msg[i].addr_page1) || + (prev_adr_pg2 != msg[i].addr_page2)) + program_scp_addr_page = true; + + for (ret = 0, try = 0; try <= mstr->retries; try++) { + + /* Call deferred or sync handler based on call */ + if (!data) + ret = mstr->driver->ops->xfer_msg(mstr, + &msg[i], program_scp_addr_page); + + else if (mstr->driver->ops->xfer_msg_deferred) + mstr->driver->ops->xfer_msg_deferred( + mstr, &msg[i], + program_scp_addr_page, + data); + else + return -ENOTSUPP; + if (ret != -EAGAIN) + break; + + if (time_after(jiffies, orig_jiffies + mstr->timeout)) + break; + } + + + /* + * Set previous address page as current once message is + * transferred. + */ + prev_adr_pg1 = msg[i].addr_page1; + prev_adr_pg2 = msg[i].addr_page2; + } + + orig_jiffies = jiffies; + + ret = 0; + + /* Reset page address if its other than 0 */ + if (msg[i].addr_page1 && msg[i].addr_page2) { + for (try = 0; try <= mstr->retries; try++) { + /* + * Reset the page address to 0, so that always there + * is fast path access to MIPI defined Slave + * registers. + */ + + ret = mstr->driver->ops->reset_page_addr( + mstr, msg[0].dev_num); + + if (ret != -EAGAIN) + break; + + if (time_after(jiffies, orig_jiffies + mstr->timeout)) + break; + } + } + + if (!ret) + return i + 1; + + return ret; +} + +/** + * sdw_bank_switch_deferred: Initiate the transfer of the message but + * doesn't wait for the message to be completed. Bus driver waits + * outside context of this API for master driver to signal message + * transfer complete. This is not Public API, this is used by Bus + * driver only for Bank switch. + * + * @mstr: Master which will transfer the message. + * @msg: Message to be transferred. Message length of only 1 is supported. + * @data: Deferred information for the message to be transferred. This is + * filled by Master on message transfer complete. + * + * Returns immediately after initiating the transfer, Bus driver needs to + * wait on xfer_complete, part of data, which is set by Master driver on + * completion of message transfer. + * + */ +void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg, + struct sdw_deferred_xfer_data *data) +{ + + pm_runtime_get_sync(&mstr->dev); + + sdw_transfer(mstr, msg, 1, data); + + pm_runtime_mark_last_busy(&mstr->dev); + pm_runtime_put_sync_autosuspend(&mstr->dev); + +} + +/** + * snd_sdw_slave_transfer: Transfer message on bus. + * + * @master: Master which will transfer the message. + * @msg: Array of messages to be transferred. + * @num: Number of messages to be transferred, messages include read and + * write messages, but not the ping commands. The read and write + * messages are transmitted as a part of read and write SoundWire + * commands with a parameter containing the payload. + * + * Returns the number of messages successfully transferred else appropriate + * error code. + */ +int snd_sdw_slave_transfer(struct sdw_master *master, struct sdw_msg *msg, + unsigned int num) +{ + int ret; + + /* + * Master reports the successfully transmitted messages onto the + * bus. If there are N message to be transmitted onto bus, and if + * Master gets error at (N-2) message it will report number of + * message transferred as N-2 Error is reported if ACK is not + * received for all messages or NACK is received for any of the + * transmitted messages. Currently both ACK not getting received + * and NACK is treated as error. But for upper level like regmap, + * both (Absence of ACK or NACK) errors are same as failure. + */ + + /* + * Make sure Master is woken up before message transfer Ideally + * function calling this should have wokenup Master as this will be + * called by Slave driver, and it will do runtime_get for itself, + * which will make sure Master is woken up as Master is parent Linux + * device of Slave. But if Slave is not implementing RTPM, it may + * not do this, so bus driver has to do it always irrespective of + * what Slave does. + */ + pm_runtime_get_sync(&master->dev); + + if (in_atomic() || irqs_disabled()) { + ret = mutex_trylock(&master->msg_lock); + if (!ret) { + ret = -EAGAIN; + goto out; + } + } else { + mutex_lock(&master->msg_lock); + } + + ret = sdw_transfer(master, msg, num, NULL); + + mutex_unlock(&master->msg_lock); +out: + /* Put Master to sleep once message is transferred */ + pm_runtime_mark_last_busy(&master->dev); + pm_runtime_put_sync_autosuspend(&master->dev); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_sdw_slave_transfer); + +/** + * snd_sdw_master_register_driver: This API will register the Master driver + * with the SoundWire bus. It is typically called from the driver's + * module-init function. * * @driver: Master Driver to be associated with Master interface. * @owner: Module owner, generally THIS module. diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h index 5911aa6..0af1c99 100644 --- a/sound/sdw/sdw_priv.h +++ b/sound/sdw/sdw_priv.h @@ -99,4 +99,148 @@ struct snd_sdw_core { struct idr idr; };
+/** + * sdw_bank_switch_deferred: Initiate the transfer of the message but + * doesn't wait for the message to be completed. Bus driver waits + * outside context of this API for master driver to signal message + * transfer complete. This is not Public API, this is used by Bus + * driver only for Bank switch. + * + * @mstr: Master which will transfer the message. + * @msg: Message to be transferred. Message length of only 1 is supported. + * @data: Deferred information for the message to be transferred. This is + * filled by Master on message transfer complete. + * + * Returns immediately after initiating the transfer, Bus driver needs to + * wait on xfer_complete, part of data, which is set by Master driver on + * completion of message transfer. + */ +void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg, + struct sdw_deferred_xfer_data *data); +/* + * Helper function for bus driver to write messages. Since bus driver + * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is + * set to 0. + */ +static inline int sdw_wr_msg(struct sdw_msg *msg, bool xmit_on_ssp, u16 addr, + u16 len, u8 *buf, u8 dev_num, + struct sdw_master *mstr, + int num_msg) +{ + msg->xmit_on_ssp = xmit_on_ssp; + msg->r_w_flag = SDW_MSG_FLAG_WRITE; + msg->addr = addr; + msg->len = len; + msg->buf = buf; + msg->dev_num = dev_num; + msg->addr_page1 = 0x0; + msg->addr_page2 = 0x0; + + return snd_sdw_slave_transfer(mstr, msg, num_msg); +} + +/* + * Helper function for bus driver to read messages. Since bus driver + * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is + * set to 0. + */ +static inline int sdw_rd_msg(struct sdw_msg *msg, bool xmit_on_ssp, u16 addr, + u16 len, u8 *buf, u8 dev_num, + struct sdw_master *mstr, + int num_msg) +{ + msg->xmit_on_ssp = xmit_on_ssp; + msg->r_w_flag = SDW_MSG_FLAG_READ; + msg->addr = addr; + msg->len = len; + msg->buf = buf; + msg->dev_num = dev_num; + msg->addr_page1 = 0x0; + msg->addr_page2 = 0x0; + + return snd_sdw_slave_transfer(mstr, msg, num_msg); +} + +/* + * Helper function for bus driver to write messages (nopm version). Since + * bus driver operates on MIPI defined Slave registers, addr_page1 and + * addr_page2 is set to 0. + */ +static inline int sdw_wr_msg_nopm(struct sdw_msg *msg, bool xmit_on_ssp, + u16 addr, u16 len, u8 *buf, + u8 dev_num, + struct sdw_master *mstr, + int num_msg) +{ + msg->xmit_on_ssp = xmit_on_ssp; + msg->r_w_flag = SDW_MSG_FLAG_WRITE; + msg->addr = addr; + msg->len = len; + msg->buf = buf; + msg->dev_num = dev_num; + msg->addr_page1 = 0x0; + msg->addr_page2 = 0x0; + + return snd_sdw_slave_transfer(mstr, msg, num_msg); +} + +/* + * Helper function for bus driver to read messages (nopm version). Since + * bus driver operates on MIPI defined Slave registers, addr_page1 and + * addr_page2 is set to 0. + */ +static inline int sdw_rd_msg_nopm(struct sdw_msg *msg, bool xmit_on_ssp, + u16 addr, u16 len, u8 *buf, + u8 dev_num, + struct sdw_master *mstr, + int num_msg) +{ + msg->xmit_on_ssp = xmit_on_ssp; + msg->r_w_flag = SDW_MSG_FLAG_READ; + msg->addr = addr; + msg->len = len; + msg->buf = buf; + msg->dev_num = dev_num; + msg->addr_page1 = 0x0; + msg->addr_page2 = 0x0; + + return snd_sdw_slave_transfer(mstr, msg, num_msg); +} + +/* + * Helper function for bus driver to create read messages. Since bus driver + * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is + * set to 0. + */ +static inline void sdw_create_rd_msg(struct sdw_msg *msg, bool xmit_on_ssp, + u16 addr, u16 len, u8 *buf, u8 dev_num) +{ + msg->xmit_on_ssp = xmit_on_ssp; + msg->r_w_flag = SDW_MSG_FLAG_READ; + msg->addr = addr; + msg->len = len; + msg->buf = buf; + msg->dev_num = dev_num; + msg->addr_page1 = 0x0; + msg->addr_page2 = 0x0; +} + +/* + * Helper function for bus driver to create write messages. Since bus driver + * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is + * set to 0. + */ +static inline void sdw_create_wr_msg(struct sdw_msg *msg, bool xmit_on_ssp, + u16 addr, u16 len, u8 *buf, u8 dev_num) +{ + msg->xmit_on_ssp = xmit_on_ssp; + msg->r_w_flag = SDW_MSG_FLAG_WRITE; + msg->addr = addr; + msg->len = len; + msg->buf = buf; + msg->dev_num = dev_num; + msg->addr_page1 = 0x0; + msg->addr_page2 = 0x0; +} + #endif /* _LINUX_SDW_PRIV_H */
This patch adds the support for updating the Slave status to bus driver. Master driver updates Slave status change to the bus driver. Bus driver takes appropriate action on Slave status change like.
1. Registering new device if new Slave got enumerated on bus. 2. Assigning the device number to the Slave device 3. Marking Slave as un-attached if Slave got detached from bus. 4. Handling Slave alerts.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/sdw/sdw.c | 1074 ++++++++++++++++++++++++++++++++++++++++++++++++++ sound/sdw/sdw_priv.h | 66 ++++ 2 files changed, 1140 insertions(+)
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c index c98e4d7..801200f 100644 --- a/sound/sdw/sdw.c +++ b/sound/sdw/sdw.c @@ -60,11 +60,13 @@ #include <linux/module.h> #include <linux/errno.h> #include <linux/slab.h> +#include <linux/delay.h> #include <linux/pm_runtime.h> #include <linux/pm_domain.h> #include <sound/sdw_bus.h> #include <sound/sdw_master.h> #include <sound/sdw_slave.h> +#include <sound/sdw/sdw_registers.h>
#include "sdw_priv.h"
@@ -334,6 +336,251 @@ static int sdw_match(struct device *dev, struct device_driver *driver) .pm = &soundwire_pm, };
+static int sdw_find_free_dev_num(struct sdw_master *mstr, + struct sdw_msg *msg) +{ + int i, ret = -EINVAL; + + mutex_lock(&mstr->lock); + + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + if (mstr->sdw_addr[i].assigned == true) + continue; + + mstr->sdw_addr[i].assigned = true; + + memcpy(mstr->sdw_addr[i].dev_id, msg->buf, + SDW_NUM_DEV_ID_REGISTERS); + + ret = i; + break; + } + + mutex_unlock(&mstr->lock); + return ret; +} + +static int sdw_program_dev_num(struct sdw_master *mstr, u8 dev_num) +{ + struct sdw_msg msg; + u8 buf; + int ret; + + buf = dev_num; + ret = sdw_wr_msg(&msg, 0, SDW_SCP_DEVNUMBER, 1, &buf, 0x0, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&mstr->dev, "Program Slave address failed ret = %d\n", ret); + return ret; + } + + return 0; +} + +static bool sdw_find_slv(struct sdw_master *mstr, struct sdw_msg *msg, + unsigned int *dev_num) +{ + struct sdw_slave_addr *sdw_addr; + int i, comparison; + bool found = false; + + mutex_lock(&mstr->lock); + + /* + * Device number resets to 0, when Slave gets unattached. Find the + * already registered Slave, mark it as present and program the + * Slave address again with same value. + */ + sdw_addr = mstr->sdw_addr; + + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + comparison = memcmp(sdw_addr[i].dev_id, msg->buf, + SDW_NUM_DEV_ID_REGISTERS); + + if ((!comparison) && (sdw_addr[i].assigned == true)) { + found = true; + *dev_num = i; + break; + } + } + + mutex_unlock(&mstr->lock); + + return found; +} + +static void sdw_free_dev_num(struct sdw_master *mstr, int dev_num) +{ + int i; + + mutex_lock(&mstr->lock); + + for (i = 0; i <= SDW_MAX_DEVICES; i++) { + + if (dev_num == mstr->sdw_addr[i].dev_num) { + + mstr->sdw_addr[dev_num].assigned = false; + memset(&mstr->sdw_addr[dev_num].dev_id[0], 0x0, + SDW_NUM_DEV_ID_REGISTERS); + break; + } + } + + mutex_unlock(&mstr->lock); +} + +static int sdw_slv_register(struct sdw_master *mstr) +{ + int ret, i; + struct sdw_msg msg; + u8 buf[SDW_NUM_DEV_ID_REGISTERS]; + struct sdw_slave *sdw_slave; + int dev_num = -1; + bool found = false; + + /* Create message to read the 6 dev_id registers */ + sdw_create_rd_msg(&msg, 0, SDW_SCP_DEVID_0, SDW_NUM_DEV_ID_REGISTERS, + buf, 0x0); + + /* + * Multiple Slaves may report an Attached_OK status as Device0. + * Since the enumeration relies on a hardware arbitration and is + * done one Slave at a time, a loop needs to run until all Slaves + * have been assigned a non-zero DeviceNumber. The loop exits when + * the reads from Device0 devID registers are no longer successful, + * i.e. there is no Slave left to enumerate + */ + while ((ret = (snd_sdw_slave_transfer(mstr, &msg, SDW_NUM_OF_MSG1_XFRD)) + == SDW_NUM_OF_MSG1_XFRD)) { + + /* + * Find is Slave is re-enumerating, and was already + * registered earlier. + */ + found = sdw_find_slv(mstr, &msg, &dev_num); + + /* + * Reprogram the Slave device number if its getting + * re-enumerated. If that fails we continue finding new + * slaves, we flag error but don't stop since there may be + * new Slaves trying to get enumerated. + */ + if (found) { + ret = sdw_program_dev_num(mstr, dev_num); + if (ret < 0) + dev_err(&mstr->dev, "Re-registering slave failed ret = %d", ret); + + continue; + + } + + /* + * Find the free device_number for the new Slave getting + * enumerated 1st time. + */ + dev_num = sdw_find_free_dev_num(mstr, &msg); + if (dev_num < 0) { + dev_err(&mstr->dev, "Failed to find free dev_num ret = %d\n", ret); + goto dev_num_assign_fail; + } + + /* + * Allocate and initialize the Slave device on first + * enumeration + */ + sdw_slave = kzalloc(sizeof(*sdw_slave), GFP_KERNEL); + if (!sdw_slave) { + ret = -ENOMEM; + goto mem_alloc_failed; + } + + /* + * Initialize the allocated Slave device, set bus type and + * device type to SoundWire. + */ + sdw_slave->mstr = mstr; + sdw_slave->dev.parent = &sdw_slave->mstr->dev; + sdw_slave->dev.bus = &sdw_bus_type; + sdw_slave->dev.type = &sdw_slv_type; + sdw_slave->priv.addr = &mstr->sdw_addr[dev_num]; + sdw_slave->priv.addr->slave = sdw_slave; + + for (i = 0; i < SDW_NUM_DEV_ID_REGISTERS; i++) + sdw_slave->priv.dev_id[i] = msg.buf[i]; + + dev_dbg(&mstr->dev, "SDW slave slave id found with values\n"); + dev_dbg(&mstr->dev, "dev_id0 to dev_id5: %x:%x:%x:%x:%x:%x\n", + msg.buf[0], msg.buf[1], msg.buf[2], + msg.buf[3], msg.buf[4], msg.buf[5]); + dev_dbg(&mstr->dev, "Dev number assigned is %x\n", dev_num); + + /* + * Set the Slave device name, its based on the dev_id and + * to bus which it is attached. + */ + dev_set_name(&sdw_slave->dev, "sdw-slave%d-%02x:%02x:%02x:%02x:%02x:%02x", + sdw_master_get_id(mstr), + sdw_slave->priv.dev_id[0], + sdw_slave->priv.dev_id[1], + sdw_slave->priv.dev_id[2], + sdw_slave->priv.dev_id[3], + sdw_slave->priv.dev_id[4], + sdw_slave->priv.dev_id[5]); + + /* + * Set name based on dev_id. This will be used in match + * function to bind the device and driver. + */ + sprintf(sdw_slave->priv.name, "%02x:%02x:%02x:%02x:%02x:%02x", + sdw_slave->priv.dev_id[0], + sdw_slave->priv.dev_id[1], + sdw_slave->priv.dev_id[2], + sdw_slave->priv.dev_id[3], + sdw_slave->priv.dev_id[4], + sdw_slave->priv.dev_id[5]); + ret = device_register(&sdw_slave->dev); + if (ret) { + dev_err(&mstr->dev, "Register slave failed ret = %d\n", ret); + goto reg_slv_failed; + } + + ret = sdw_program_dev_num(mstr, dev_num); + if (ret < 0) { + dev_err(&mstr->dev, "Programming slave address failed ret = %d\n", ret); + goto program_slv_failed; + } + + dev_dbg(&mstr->dev, "Slave registered with bus id %s\n", + dev_name(&sdw_slave->dev)); + + sdw_slave->dev_num = dev_num; + + /* + * Max number of Slaves that can be attached is 11. This + * check is performed in sdw_find_free_dev_num function. + */ + mstr->num_slv++; + + mutex_lock(&mstr->lock); + list_add_tail(&sdw_slave->priv.node, &mstr->slv_list); + mutex_unlock(&mstr->lock); + + } + + return ret; + +program_slv_failed: + device_unregister(&sdw_slave->dev); +reg_slv_failed: + kfree(sdw_slave); +mem_alloc_failed: + sdw_free_dev_num(mstr, dev_num); +dev_num_assign_fail: + return ret; + +} + /** * sdw_transfer: Local function where logic is placed to handle NOPM and PM * variants of the Slave transfer functions. @@ -516,6 +763,816 @@ int snd_sdw_slave_transfer(struct sdw_master *master, struct sdw_msg *msg, } EXPORT_SYMBOL_GPL(snd_sdw_slave_transfer);
+static int sdw_handle_dp0_interrupts(struct sdw_master *mstr, + struct sdw_slave *sdw_slv, unsigned int *status) +{ + int ret; + struct sdw_msg rd_msg, wr_msg; + int impl_def_mask = 0; + u8 rbuf, wbuf; + struct sdw_slave_dp0_caps *dp0_cap; + struct sdw_slave_priv *slv_priv = &sdw_slv->priv; + + dp0_cap = slv_priv->caps.dp0_caps; + + /* Read the DP0 interrupt status register and parse the bits */ + ret = sdw_rd_msg(&rd_msg, 0x0, SDW_DP0_INTSTAT, 1, &rbuf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&mstr->dev, "Intr status read failed for slave %x\n", + sdw_slv->dev_num); + goto out; + } + + if (rd_msg.buf[0] & SDW_DP0_INTSTAT_TEST_FAIL_MASK) { + dev_err(&mstr->dev, "Test fail for slave %d port 0\n", + sdw_slv->dev_num); + wr_msg.buf[0] |= SDW_DP0_INTCLEAR_TEST_FAIL_MASK; + } + + if ((dp0_cap->prepare_ch == SDW_CP_MODE_NORMAL) && + (rd_msg.buf[0] & SDW_DP0_INTSTAT_PORT_READY_MASK)) { + complete(&slv_priv->port_ready[0]); + wr_msg.buf[0] |= SDW_DP0_INTCLEAR_PORT_READY_MASK; + } + + if (rd_msg.buf[0] & SDW_DP0_INTMASK_BRA_FAILURE_MASK) { + /* TODO: Handle BRA failure */ + dev_err(&mstr->dev, "BRA failed for slave %d\n", + sdw_slv->dev_num); + wr_msg.buf[0] |= SDW_DP0_INTCLEAR_BRA_FAILURE_MASK; + } + + impl_def_mask = SDW_DP0_INTSTAT_IMPDEF1_MASK | + SDW_DP0_INTSTAT_IMPDEF2_MASK | + SDW_DP0_INTSTAT_IMPDEF3_MASK; + if (rd_msg.buf[0] & impl_def_mask) { + wr_msg.buf[0] |= impl_def_mask; + *status = wr_msg.buf[0]; + } + + /* Ack DP0 interrupts */ + ret = sdw_wr_msg(&wr_msg, 0x0, SDW_DP0_INTCLEAR, 1, &wbuf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&mstr->dev, "Ack DP0 interrupts failed\n"); + goto out; + } + +out: + return ret; + +} + +static int sdw_handle_port_interrupts(struct sdw_master *mstr, + struct sdw_slave *sdw_slv, int port_num, + unsigned int *status) +{ + int ret; + struct sdw_msg rd_msg, wr_msg; + u8 rbuf, wbuf; + int impl_def_mask = 0; + u16 intr_clr_addr, intr_stat_addr; + struct sdw_slave_priv *slv_priv = &sdw_slv->priv; + + /* + * Handle the Data port0 interrupt separately since the interrupt + * mask and stat register is different than other DPn registers + */ + if (port_num == 0 && slv_priv->caps.dp0_present) + return sdw_handle_dp0_interrupts(mstr, sdw_slv, status); + + intr_stat_addr = SDW_DPN_INTSTAT + (SDW_NUM_DATA_PORT_REGISTERS * + port_num); + + /* Read the interrupt status register of port and parse bits */ + ret = sdw_rd_msg(&rd_msg, 0x0, intr_stat_addr, 1, &rbuf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&mstr->dev, "Port Status read failed for slv %x port %x\n", + sdw_slv->dev_num, port_num); + goto out; + } + + if (rd_msg.buf[0] & SDW_DPN_INTSTAT_TEST_FAIL_MASK) { + dev_err(&mstr->dev, "Test fail for slave %x port %x\n", + sdw_slv->dev_num, port_num); + wr_msg.buf[0] |= SDW_DPN_INTCLEAR_TEST_FAIL_MASK; + } + + /* + * Port Ready interrupt is only for Normal Channel prepare state + * machine + */ + if ((rd_msg.buf[0] & SDW_DPN_INTSTAT_PORT_READY_MASK)) { + complete(&slv_priv->port_ready[port_num]); + wr_msg.buf[0] |= SDW_DPN_INTCLEAR_PORT_READY_MASK; + } + + impl_def_mask = SDW_DPN_INTSTAT_IMPDEF1_MASK | + SDW_DPN_INTSTAT_IMPDEF2_MASK | + SDW_DPN_INTSTAT_IMPDEF3_MASK; + if (rd_msg.buf[0] & impl_def_mask) { + wr_msg.buf[0] |= impl_def_mask; + *status = wr_msg.buf[0]; + } + + intr_clr_addr = SDW_DPN_INTCLEAR + + (SDW_NUM_DATA_PORT_REGISTERS * port_num); + + /* Clear and Ack the Port interrupt */ + ret = sdw_wr_msg(&wr_msg, 0x0, intr_clr_addr, 1, &wbuf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&mstr->dev, "Clear and ACK port interrupt failed for slv %x port %x\n", + sdw_slv->dev_num, port_num); + goto out; + } + +out: + return ret; + +} + +/* + * Get the Slave status + */ +static int sdw_get_slv_intr_stat(struct sdw_master *mstr, struct sdw_slave *slv, + u8 *intr_stat_buf) +{ + struct sdw_msg rd_msg[3]; + int ret; + int num_rd_messages = 1; + struct sdw_slave_priv *slv_priv = &slv->priv; + + sdw_create_rd_msg(&rd_msg[0], 0x0, SDW_SCP_INTSTAT1, 1, + &intr_stat_buf[0], slv->dev_num); + + /* + * Create read message for reading the Instat2 registers if Slave + * supports more than 4 ports + */ + if (slv_priv->caps.num_ports > SDW_CASC_PORT_START_INTSTAT2) { + sdw_create_rd_msg(&rd_msg[1], 0x0, SDW_SCP_INTSTAT2, 1, + &intr_stat_buf[1], slv->dev_num); + num_rd_messages = 2; + + } + + if (slv_priv->caps.num_ports > SDW_CASC_PORT_START_INTSTAT3) { + sdw_create_rd_msg(&rd_msg[2], 0x0, SDW_SCP_INTSTAT3, 1, + &intr_stat_buf[2], slv->dev_num); + num_rd_messages = 3; + } + + /* Read Instat1, 2 and 3 registers */ + ret = snd_sdw_slave_transfer(mstr, rd_msg, num_rd_messages); + if (ret != num_rd_messages) { + ret = -EINVAL; + dev_err(&mstr->dev, "Intr Status read failed for slv %x\n", slv->dev_num); + } + + return ret; + +} + +static int sdw_ack_slv_intr(struct sdw_master *mstr, u8 dev_num, + u8 *intr_clr_buf) +{ + struct sdw_msg wr_msg; + int ret; + + /* Ack the interrupts */ + ret = sdw_wr_msg(&wr_msg, 0x0, SDW_SCP_INTCLEAR1, 1, + intr_clr_buf, dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + ret = -EINVAL; + dev_err(&mstr->dev, "Intr clear write failed for slv\n"); + } + + return ret; + +} + +static int sdw_handle_casc_port_intr(struct sdw_master *mstr, struct sdw_slave + *sdw_slv, u8 cs_port_start, + unsigned int *port_status, + u8 *intr_stat_buf) +{ + int i, ret; + int cs_port_mask, cs_port_reg_offset, num_cs_ports; + + switch (cs_port_start) { + + case SDW_CASC_PORT_START_INTSTAT1: + /* Number of port status bits in this register */ + num_cs_ports = SDW_NUM_CASC_PORT_INTSTAT1; + /* Bit mask for the starting port intr status */ + cs_port_mask = SDW_CASC_PORT_MASK_INTSTAT1; + /* Register offset to read Cascaded instat 1 */ + cs_port_reg_offset = SDW_CASC_PORT_REG_OFFSET_INTSTAT1; + break; + + case SDW_CASC_PORT_START_INTSTAT2: + num_cs_ports = SDW_NUM_CASC_PORT_INTSTAT2; + cs_port_mask = SDW_CASC_PORT_MASK_INTSTAT2; + cs_port_reg_offset = SDW_CASC_PORT_REG_OFFSET_INTSTAT2; + break; + + case SDW_CASC_PORT_START_INTSTAT3: + num_cs_ports = SDW_NUM_CASC_PORT_INTSTAT3; + cs_port_mask = SDW_CASC_PORT_MASK_INTSTAT3; + cs_port_reg_offset = SDW_CASC_PORT_REG_OFFSET_INTSTAT3; + break; + + default: + return -EINVAL; + + } + + /* + * Look for cascaded port interrupts, if found handle port + * interrupts. Do this for all the Int_stat registers. + */ + for (i = cs_port_start; i < cs_port_start + num_cs_ports; i++) { + if (intr_stat_buf[cs_port_reg_offset] & cs_port_mask) { + ret = sdw_handle_port_interrupts(mstr, sdw_slv, + cs_port_start + i, + &port_status[i]); + if (ret < 0) { + dev_err(&mstr->dev, "Handling port intr failed ret = %d\n", ret); + return ret; + } + } + cs_port_mask = cs_port_mask << i; + } + return 0; +} + +static int sdw_handle_impl_def_intr(struct sdw_slave *sdw_slv, + struct sdw_impl_def_intr_stat *intr_status, + unsigned int *port_status, + u8 *control_port_stat) +{ + int ret, i; + struct sdw_slave_priv *slv_priv = &sdw_slv->priv; + + /* Update the implementation defined status to Slave */ + for (i = 1; i < slv_priv->caps.num_ports; i++) { + + intr_status->portn_stat[i].status = port_status[i]; + intr_status->portn_stat[i].num = i; + } + + intr_status->port0_stat = port_status[0]; + intr_status->control_port_stat = control_port_stat[0]; + + ret = slv_priv->driver->slave_irq(sdw_slv, intr_status); + if (ret < 0) { + dev_err(&sdw_slv->mstr->dev, "Impl defined interrupt handling failed ret = %d\n", ret); + return ret; + } + return 0; +} + +/* + * sdw_handle_slv_alerts: This function handles the Slave alert. Following + * things are done as part of handling Slave alert. Attempt is done to + * complete the interrupt handling in as less read/writes as possible + * based on number of ports defined by Slave. + * + * 1. Get the interrupt status of the Slave (sdw_get_slv_intr_stat) 1a. + * Read Instat1, Instat2 and Intstat3 registers based on on number of + * ports defined by the Slave. + * + * 2. Parse Interrupt Status registers for the SCP interrupts and take + * action. + * + * 3. Parse the interrupt status registers for the Port interrupts and + * take action. + * + * 4. Ack port interrupts. + * 5. Call the Slave implementation defined interrupt, if Slave has + * registered for it. + * + * 6. Ack the Slave interrupt. + * 7. Get interrupt status of the Slave again, to make sure no new + * interrupt came when we were servicing the interrupts. + * + * 8. Goto step 2 if any interrupt pending. + * + * 9. Return if no new interrupt pending. + * TODO: Poorly-designed or faulty Slaves may continuously generate + * interrupts and delay handling of interrupts signaled by other + * Slaves. A better QoS could rely on a priority scheme, where Slaves + * with the lowest DeviceNumber are handled first. Currently the + * priority is based on the enumeration sequence and arbitration; + * additional information would be needed from firmware/BIOS or module + * parameters to rank Slaves by relative interrupt processing priority. + */ +static int sdw_handle_slv_alerts(struct sdw_master *mstr, + struct sdw_slave *sdw_slv) +{ + int slave_stat, count = 0, ret; + int max_tries = SDW_INTR_STAT_READ_MAX_TRIES; + unsigned int port_status[SDW_MAX_DATA_PORTS] = {0}; + struct sdw_impl_def_intr_stat intr_status; + struct sdw_portn_intr_stat portn_stat; + struct sdw_slave_priv *slv_priv = &sdw_slv->priv; + u8 intr_clr_buf[SDW_NUM_INT_CLEAR_REGISTERS]; + u8 intr_stat_buf[SDW_NUM_INT_STAT_REGISTERS] = {0}; + u8 cs_port_start; + + mstr->sdw_addr[sdw_slv->dev_num].status = SDW_SLAVE_STAT_ALERT; + /* + * Keep on servicing interrupts till Slave interrupts are ACKed and + * device returns to attached state instead of ALERT state + */ + ret = sdw_get_slv_intr_stat(mstr, sdw_slv, intr_stat_buf); + if (ret < 0) + return ret; + + do { + + if (intr_stat_buf[0] & SDW_SCP_INTSTAT1_PARITY_MASK) { + dev_err(&mstr->dev, "Parity error detected\n"); + intr_clr_buf[0] |= SDW_SCP_INTCLEAR1_PARITY_MASK; + } + + if (intr_stat_buf[0] & SDW_SCP_INTSTAT1_BUS_CLASH_MASK) { + dev_err(&mstr->dev, "Bus clash error detected\n"); + intr_clr_buf[0] |= SDW_SCP_INTCLEAR1_BUS_CLASH_MASK; + } + + /* Handle implementation defined mask */ + if (intr_stat_buf[0] & SDW_SCP_INTSTAT1_IMPL_DEF_MASK) + intr_clr_buf[0] |= SDW_SCP_INTCLEAR1_IMPL_DEF_MASK; + + cs_port_start = SDW_NUM_CASC_PORT_INTSTAT1; + + /* Handle Cascaded Port interrupts from Instat_1 registers */ + ret = sdw_handle_casc_port_intr(mstr, sdw_slv, cs_port_start, + port_status, intr_stat_buf); + if (ret < 0) + return ret; + + /* + * If there are more than 4 ports and cascaded interrupt is + * set, handle those interrupts + */ + if (intr_stat_buf[0] & SDW_SCP_INTSTAT1_SCP2_CASCADE_MASK) { + cs_port_start = SDW_NUM_CASC_PORT_INTSTAT2; + ret = sdw_handle_casc_port_intr(mstr, sdw_slv, + cs_port_start, port_status, + intr_stat_buf); + } + + /* + * Handle cascaded interrupts from instat_2 register, if no + * cascaded interrupt from SCP2 cascade move to impl_def + * intrs + */ + if (intr_stat_buf[1] & SDW_SCP_INTSTAT2_SCP3_CASCADE_MASK) { + cs_port_start = SDW_NUM_CASC_PORT_INTSTAT3; + ret = sdw_handle_casc_port_intr(mstr, sdw_slv, + cs_port_start, port_status, + intr_stat_buf); + } + + /* + * Handle implementation defined interrupts if Slave has + * registered for it. + */ + intr_status.portn_stat = &portn_stat; + if (slv_priv->driver->slave_irq) { + + ret = sdw_handle_impl_def_intr(sdw_slv, &intr_status, + port_status, intr_clr_buf); + if (ret < 0) + return ret; + } + + /* Ack the Slave interrupt */ + ret = sdw_ack_slv_intr(mstr, sdw_slv->dev_num, intr_clr_buf); + if (ret < 0) { + dev_err(&mstr->dev, "Slave interrupt ack failed ret = %d\n", ret); + return ret; + } + + /* + * Read status once again before exiting loop to make sure + * no new interrupts came while we were servicing the + * interrupts + */ + ret = sdw_get_slv_intr_stat(mstr, sdw_slv, intr_stat_buf); + if (ret < 0) + return ret; + + /* Make sure no interrupts are pending */ + slave_stat = intr_stat_buf[0] || intr_stat_buf[1] || + intr_stat_buf[2]; + /* + * Exit loop if Slave is continuously in ALERT state even + * after servicing the interrupt multiple times. + */ + count++; + + } while (slave_stat != 0 && count < max_tries); + + return 0; +} + +/* + * Enable the Slave Control Port (SCP) interrupts and DP0 interrupts if + * Slave supports DP0. Enable implementation defined interrupts based on + * Slave interrupt mask. + * This function enables below interrupts. + * 1. Bus clash interrupt for SCP + * 2. Parity interrupt for SCP. + * 3. Enable implementation defined interrupt if slave requires. + * 4. Port ready interrupt for the DP0 if required based on Slave support + * for DP0 and normal channel prepare supported by DP0 port. For simplified + * channel prepare Port ready interrupt is not required to be enabled. + */ +static int sdw_enable_scp_intr(struct sdw_slave *sdw_slv, int mask) +{ + struct sdw_msg rd_msg, wr_msg; + u8 buf; + int ret; + struct sdw_master *mstr = sdw_slv->mstr; + struct sdw_slave_priv *slv_priv = &sdw_slv->priv; + u32 reg_addr = SDW_SCP_INTMASK1; + + + ret = sdw_rd_msg(&rd_msg, 0, reg_addr, 1, &buf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&mstr->dev, "SCP Intr mask read failed for slave %x\n", + sdw_slv->dev_num); + return -EINVAL; + } + + buf |= mask; + + buf |= SDW_SCP_INTMASK1_BUS_CLASH_MASK; + buf |= SDW_SCP_INTMASK1_PARITY_MASK; + + ret = sdw_wr_msg(&wr_msg, 0, reg_addr, 1, &buf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&mstr->dev, "SCP Intr mask write failed for slave %x\n", + sdw_slv->dev_num); + return -EINVAL; + } + + if (!slv_priv->caps.dp0_present) + return 0; + + reg_addr = SDW_DP0_INTMASK; + mask = slv_priv->caps.dp0_caps->imp_def_intr_mask; + buf = 0; + + ret = sdw_rd_msg(&rd_msg, 0, reg_addr, 1, &buf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&mstr->dev, "DP0 Intr mask read failed for slave %x\n", + sdw_slv->dev_num); + return -EINVAL; + } + + buf |= mask; + + if (slv_priv->caps.dp0_caps->prepare_ch == SDW_CP_MODE_NORMAL) + buf |= SDW_DPN_INTMASK_PORT_READY_MASK; + + ret = sdw_wr_msg(&wr_msg, 0, reg_addr, 1, &buf, + sdw_slv->dev_num, + mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&mstr->dev, "DP0 Intr mask write failed for slave %x\n", + sdw_slv->dev_num); + return -EINVAL; + } + + return 0; +} + +int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num, + int port_direction, bool enable) +{ + + struct sdw_msg rd_msg, wr_msg; + u8 buf; + int ret; + struct sdw_master *mstr = sdw_slv->mstr; + struct sdw_slave_priv *slv_priv = &sdw_slv->priv; + u32 reg_addr; + struct sdw_dpn_caps *dpn_caps; + u8 mask; + + reg_addr = SDW_DPN_INTMASK + + (SDW_NUM_DATA_PORT_REGISTERS * port_num); + + dpn_caps = &slv_priv->caps.dpn_caps[port_direction][port_num]; + mask = dpn_caps->imp_def_intr_mask; + + /* Read DPn interrupt mask register */ + ret = sdw_rd_msg(&rd_msg, 0, reg_addr, 1, &buf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&mstr->dev, "DPn Intr mask read failed for slave %x\n", + sdw_slv->dev_num); + return -EINVAL; + } + + /* Enable the Slave defined interrupts. */ + buf |= mask; + + /* + * Enable port prepare interrupt only if port is not having + * simplified channel prepare state machine + */ + if (dpn_caps->prepare_ch == SDW_CP_MODE_NORMAL) + buf |= SDW_DPN_INTMASK_PORT_READY_MASK; + + /* Enable DPn interrupt */ + ret = sdw_wr_msg(&wr_msg, 0, reg_addr, 1, &buf, + sdw_slv->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&mstr->dev, "DPn Intr mask write failed for slave %x\n", + sdw_slv->dev_num); + return -EINVAL; + } + return 0; +} + +/** + * snd_sdw_slave_set_intr_mask: Set the implementation defined interrupt + * mask. Slave sets the implementation defined interrupt mask as part + * of registering Slave capabilities. Slave driver can also modify + * implementation defined interrupt dynamically using below function. + * + * @slave: SoundWire Slave handle for which interrupt needs to be enabled. + * @intr_mask: Implementation defined interrupt mask. + */ +int snd_sdw_slave_set_intr_mask(struct sdw_slave *slave, + struct sdw_impl_def_intr_mask *intr_mask) +{ + int ret, i, j; + struct sdw_slave_caps *caps = &slave->priv.caps; + struct sdw_slave_dp0_caps *dp0_caps = caps->dp0_caps; + u8 ports; + + caps->scp_impl_def_intr_mask = intr_mask->control_port_mask; + + if (caps->dp0_present) + dp0_caps->imp_def_intr_mask = intr_mask->port0_mask; + + for (i = 0; i < SDW_MAX_PORT_DIRECTIONS; i++) { + if (i == 0) + ports = caps->num_src_ports; + else + ports = caps->num_sink_ports; + for (j = 0; j < ports; j++) { + caps->dpn_caps[i][j].imp_def_intr_mask = + intr_mask->portn_mask[i][j].mask; + } + } + + ret = sdw_enable_scp_intr(slave, caps->scp_impl_def_intr_mask); + if (ret < 0) + return ret; + + return 0; + +} +EXPORT_SYMBOL(snd_sdw_slave_set_intr_mask); + +static int sdw_program_slv(struct sdw_slave *sdw_slv) +{ + struct sdw_slave_caps *cap; + int ret; + struct sdw_master *mstr = sdw_slv->mstr; + struct sdw_slave_priv *slv_priv = &sdw_slv->priv; + + cap = &slv_priv->caps; + + /* Enable DP0 and SCP interrupts */ + ret = sdw_enable_scp_intr(sdw_slv, cap->scp_impl_def_intr_mask); + if (ret < 0) { + dev_err(&mstr->dev, "SCP program failed ret = %d\n", ret); + return ret; + } + + return ret; +} + +static void sdw_update_slv_status_event(struct sdw_slave *slave, + enum sdw_slave_status status) + +{ + struct sdw_slave_priv *slv_priv = &slave->priv; + struct sdw_slave_driver *slv_drv = slv_priv->driver; + + if (slv_drv->status_change_event) + slv_drv->status_change_event(slave, status); +} + + +/* + * Following thing are done in below loop for each of the registered Slaves. + * This handles only Slaves which were already registered before update + * status. + * 1. Mark Slave as not present, if status is unattached from from bus and + * logical address assigned is true, update status to Slave driver. + * + * 2. Handle the Slave alerts, if the Status is Alert for any of the Slaves. + * 3. Mark the Slave as present, if Status is Present and logical address is + * assigned. + * 3a. Update the Slave status to driver, driver will use to make sure + * its enumerated before doing read/writes. + * + * 3b. De-prepare if the Slave is exiting from clock stop mode 1 and + * capability is updated as "de-prepare" required after exiting clock + * stop mode 1. + * + * 3c. Program Slave registers for the implementation defined + * interrupts and wake enable based on Slave capabilities. + */ +static void sdw_process_slv_status(struct sdw_master *mstr, + struct sdw_slv_status *status) +{ + int i, ret; + + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + + if (mstr->sdw_addr[i].assigned != true) + continue; + /* + * If current state of device is same as previous + * state, nothing to be done for this device. + */ + else if (status->status[i] == mstr->sdw_addr[i].status) + continue; + + /* + * If Slave got unattached, mark it as not present + * Slave can get unattached from attached state or + * Alert State + */ + if (status->status[i] == SDW_SLAVE_STAT_NOT_PRESENT) { + + mstr->sdw_addr[i].status = + SDW_SLAVE_STAT_NOT_PRESENT; + + /* + * If Slave is in alert state, handle the Slave + * interrupts. Slave can get into alert state from + * attached state only. + */ + } else if (status->status[i] == SDW_SLAVE_STAT_ALERT) { + + ret = sdw_handle_slv_alerts(mstr, + mstr->sdw_addr[i].slave); + + /* + * If Slave is re-attaching on the bus program all + * the interrupt and wake_en registers based on + * capabilities. De-prepare the Slave based on + * capability. Slave can move from Alert to + * Attached_Ok, but nothing needs to be done on that + * transition, it can also move from Not_present to + * Attached_ok, in this case only registers needs to + * be reprogrammed and deprepare needs to be done. + */ + } else if (status->status[i] == + SDW_SLAVE_STAT_ATTACHED_OK && + mstr->sdw_addr[i].status == + SDW_SLAVE_STAT_NOT_PRESENT) { + + ret = sdw_program_slv(mstr->sdw_addr[i].slave); + + if (ret < 0) + continue; + + mstr->sdw_addr[i].status = + SDW_SLAVE_STAT_ATTACHED_OK; + } + + /* + * Update the status to Slave, This is used by Slave + * during resume to make sure its enumerated before + * Slave register access + */ + sdw_update_slv_status_event(mstr->sdw_addr[i].slave, + mstr->sdw_addr[i].status); + } +} + +/* + * sdw_handle_slv_status: Worker thread to handle the Slave status. + */ +static void sdw_handle_slv_status(struct kthread_work *work) +{ + int ret = 0; + struct sdw_slv_status *status, *__status__; + struct sdw_bus *bus = + container_of(work, struct sdw_bus, kwork); + struct sdw_master *mstr = bus->mstr; + unsigned long flags; + + /* + * Loop through each of the status nodes. Each node contains status + * for all Slaves. Master driver reports Slave status for all Slaves + * in interrupt context. Bus driver adds it to list and schedules + * this thread. + */ + list_for_each_entry_safe(status, __status__, &bus->status_list, node) { + + /* + * Handle newly attached Slaves, Register the Slaves with + * bus for all newly attached Slaves. Slaves may be + * attaching first time to bus or may have re-enumerated + * after hard or soft reset or clock stop exit 1. + */ + if (status->status[0] == SDW_SLAVE_STAT_ATTACHED_OK) { + ret = sdw_slv_register(mstr); + if (ret < 0) + /* + * Even if adding new Slave fails, we will + * continue to add Slaves till we find all + * the enumerated Slaves. + */ + dev_err(&mstr->dev, "Register new slave failed ret = %d\n", ret); + } + + sdw_process_slv_status(mstr, status); + + spin_lock_irqsave(&bus->spinlock, flags); + list_del(&status->node); + spin_unlock_irqrestore(&bus->spinlock, flags); + kfree(status); + } +} + +/** + * snd_sdw_master_update_slave_status: Update the status of the Slave to the + * bus driver. Master calls this function based on the interrupt it + * gets once the Slave changes its state or from interrupts for the + * Master hardware that caches status information reported in PING + * commands. + * + * @master: Master handle for which status is reported. + * @status: Array of status of each Slave. + * + * This function can be called from interrupt context by Master driver to + * report Slave status without delay. + */ +int snd_sdw_master_update_slave_status(struct sdw_master *master, + struct sdw_status *status) +{ + struct sdw_bus *bus = NULL; + struct sdw_slv_status *slv_status; + unsigned long flags; + + bus = master->bus; + + slv_status = kzalloc(sizeof(*slv_status), GFP_ATOMIC); + if (!slv_status) + return -ENOMEM; + + memcpy(slv_status->status, status, sizeof(*slv_status)); + + /* + * Bus driver will take appropriate action for Slave status change + * in thread context. Master driver can call this from interrupt + * context as well. Memory for the Slave status will be freed in + * workqueue, once its handled. + */ + spin_lock_irqsave(&bus->spinlock, flags); + list_add_tail(&slv_status->node, &bus->status_list); + spin_unlock_irqrestore(&bus->spinlock, flags); + + kthread_queue_work(&bus->kworker, &bus->kwork); + return 0; +} +EXPORT_SYMBOL_GPL(snd_sdw_master_update_slave_status); + /** * snd_sdw_master_register_driver: This API will register the Master driver * with the SoundWire bus. It is typically called from the driver's @@ -910,6 +1967,21 @@ int snd_sdw_master_add(struct sdw_master *master)
dev_dbg(&master->dev, "master [%s] registered\n", master->name);
+ kthread_init_worker(&sdw_bus->kworker); + sdw_bus->status_thread = kthread_run(kthread_worker_fn, + &sdw_bus->kworker, "%s", + dev_name(&master->dev)); + + if (IS_ERR(sdw_bus->status_thread)) { + dev_err(&master->dev, "error: failed to create status message task\n"); + ret = PTR_ERR(sdw_bus->status_thread); + goto thread_create_failed; + } + + kthread_init_work(&sdw_bus->kwork, sdw_handle_slv_status); + INIT_LIST_HEAD(&sdw_bus->status_list); + spin_lock_init(&sdw_bus->spinlock); + /* * Add bus to the list of buses inside core. This is list of Slave * devices enumerated on this bus. Adding new devices at end. It can @@ -920,6 +1992,8 @@ int snd_sdw_master_add(struct sdw_master *master)
return 0;
+thread_create_failed: + device_unregister(&master->dev); dev_reg_failed: kfree(sdw_bus); alloc_failed: diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h index 0af1c99..bbba27a 100644 --- a/sound/sdw/sdw_priv.h +++ b/sound/sdw/sdw_priv.h @@ -59,6 +59,27 @@ #ifndef _LINUX_SDW_PRIV_H #define _LINUX_SDW_PRIV_H
+#include <linux/kthread.h> +#include <linux/spinlock.h> + +/* Number of message(s) transferred on bus. */ +#define SDW_NUM_OF_MSG1_XFRD 1 +#define SDW_NUM_OF_MSG2_XFRD 2 +#define SDW_NUM_OF_MSG3_XFRD 3 +#define SDW_NUM_OF_MSG4_XFRD 4 + +/* Maximum number of Data Ports. */ +#define SDW_MAX_DATA_PORTS 15 + +/** + * Max retries to service Slave interrupts, once Slave is in ALERT state. + * Bus driver tries to service interrupt till Slave state is changed to + * "ATTACHED_OK". In case Slave remains in ALERT state because of error + * condition on Slave like, PLL not getting locked or continuous Jack + * sensing, bus driver exits after MAX retries. + */ +#define SDW_INTR_STAT_READ_MAX_TRIES 10 + /** * sdw_driver: Structure to typecast both Master and Slave driver to generic * SoundWire driver, to find out the driver type. @@ -72,16 +93,44 @@ struct sdw_driver { }; #define to_sdw_driver(d) \ container_of(d, struct sdw_driver, driver) + +/** + * sdw_slv_status: List of Slave status. + * + * @node: Node for adding status to list of Slave status. + * @status: Slave status. + */ +struct sdw_slv_status { + struct list_head node; + enum sdw_slave_status status[SDW_MAX_DEVICES]; +}; + /** * sdw_bus: Bus structure holding bus related information. * * @bus_node: Node to add the bus in the sdw_core list. * @mstr: Master reference for the bus. + * @status_thread: Thread to process the Slave status. + * @kworker: Worker for updating the Slave status. + * @kwork: Work for worker + * @status_list: List where status update from master is added. List is + * executed one by one. + * + * @spinlock: Lock to protect the list between work thread and interrupt + * context. Bus driver does Slave status processing in the thread + * context, spinlock is used to put the status reported by Master into + * the status list which is processed by bus driver in thread context + * later. */
struct sdw_bus { struct list_head bus_node; struct sdw_master *mstr; + struct task_struct *status_thread; + struct kthread_worker kworker; + struct kthread_work kwork; + struct list_head status_list; + spinlock_t spinlock; };
/** @@ -117,6 +166,23 @@ struct snd_sdw_core { */ void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg, struct sdw_deferred_xfer_data *data); + +/** + * sdw_enable_disable_dpn_intr: Enable or Disable Slave Data Port interrupt. + * This is called by bus driver before prepare and after deprepare of + * the ports. + * + * @sdw_slv: Slave handle. + * @port_num: Port number. + * @port_direction: Direction of the port configuration while doing + * prepare/deprepare. + * + * @enable: Enable (1) or disable (0) the port interrupts. + */ +int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num, + int port_direction, bool enable); + + /* * Helper function for bus driver to write messages. Since bus driver * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is
On Fri, Oct 21, 2016 at 06:11:07PM +0530, Hardik Shah wrote:
This patch adds the support for updating the Slave status to bus driver. Master driver updates Slave status change to the bus driver. Bus driver takes appropriate action on Slave status change like.
- Registering new device if new Slave got enumerated on bus.
- Assigning the device number to the Slave device
- Marking Slave as un-attached if Slave got detached from bus.
- Handling Slave alerts.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
sound/sdw/sdw.c | 1074 ++++++++++++++++++++++++++++++++++++++++++++++++++ sound/sdw/sdw_priv.h | 66 ++++ 2 files changed, 1140 insertions(+)
<snip>
+static int sdw_slv_register(struct sdw_master *mstr) +{
- int ret, i;
- struct sdw_msg msg;
- u8 buf[SDW_NUM_DEV_ID_REGISTERS];
- struct sdw_slave *sdw_slave;
- int dev_num = -1;
- bool found = false;
- /* Create message to read the 6 dev_id registers */
- sdw_create_rd_msg(&msg, 0, SDW_SCP_DEVID_0, SDW_NUM_DEV_ID_REGISTERS,
buf, 0x0);
- /*
* Multiple Slaves may report an Attached_OK status as Device0.
* Since the enumeration relies on a hardware arbitration and is
* done one Slave at a time, a loop needs to run until all Slaves
* have been assigned a non-zero DeviceNumber. The loop exits when
* the reads from Device0 devID registers are no longer successful,
* i.e. there is no Slave left to enumerate
*/
- while ((ret = (snd_sdw_slave_transfer(mstr, &msg, SDW_NUM_OF_MSG1_XFRD))
== SDW_NUM_OF_MSG1_XFRD)) {
/*
* Find is Slave is re-enumerating, and was already
* registered earlier.
*/
found = sdw_find_slv(mstr, &msg, &dev_num);
/*
* Reprogram the Slave device number if its getting
* re-enumerated. If that fails we continue finding new
* slaves, we flag error but don't stop since there may be
* new Slaves trying to get enumerated.
*/
if (found) {
ret = sdw_program_dev_num(mstr, dev_num);
if (ret < 0)
dev_err(&mstr->dev, "Re-registering slave failed ret = %d", ret);
continue;
}
/*
* Find the free device_number for the new Slave getting
* enumerated 1st time.
*/
dev_num = sdw_find_free_dev_num(mstr, &msg);
if (dev_num < 0) {
dev_err(&mstr->dev, "Failed to find free dev_num ret = %d\n", ret);
goto dev_num_assign_fail;
}
/*
* Allocate and initialize the Slave device on first
* enumeration
*/
sdw_slave = kzalloc(sizeof(*sdw_slave), GFP_KERNEL);
if (!sdw_slave) {
ret = -ENOMEM;
goto mem_alloc_failed;
}
/*
* Initialize the allocated Slave device, set bus type and
* device type to SoundWire.
*/
sdw_slave->mstr = mstr;
sdw_slave->dev.parent = &sdw_slave->mstr->dev;
sdw_slave->dev.bus = &sdw_bus_type;
sdw_slave->dev.type = &sdw_slv_type;
sdw_slave->priv.addr = &mstr->sdw_addr[dev_num];
sdw_slave->priv.addr->slave = sdw_slave;
for (i = 0; i < SDW_NUM_DEV_ID_REGISTERS; i++)
sdw_slave->priv.dev_id[i] = msg.buf[i];
dev_dbg(&mstr->dev, "SDW slave slave id found with values\n");
dev_dbg(&mstr->dev, "dev_id0 to dev_id5: %x:%x:%x:%x:%x:%x\n",
msg.buf[0], msg.buf[1], msg.buf[2],
msg.buf[3], msg.buf[4], msg.buf[5]);
dev_dbg(&mstr->dev, "Dev number assigned is %x\n", dev_num);
/*
* Set the Slave device name, its based on the dev_id and
* to bus which it is attached.
*/
dev_set_name(&sdw_slave->dev, "sdw-slave%d-%02x:%02x:%02x:%02x:%02x:%02x",
sdw_master_get_id(mstr),
sdw_slave->priv.dev_id[0],
sdw_slave->priv.dev_id[1],
sdw_slave->priv.dev_id[2],
sdw_slave->priv.dev_id[3],
sdw_slave->priv.dev_id[4],
sdw_slave->priv.dev_id[5]);
/*
* Set name based on dev_id. This will be used in match
* function to bind the device and driver.
*/
sprintf(sdw_slave->priv.name, "%02x:%02x:%02x:%02x:%02x:%02x",
sdw_slave->priv.dev_id[0],
sdw_slave->priv.dev_id[1],
sdw_slave->priv.dev_id[2],
sdw_slave->priv.dev_id[3],
sdw_slave->priv.dev_id[4],
sdw_slave->priv.dev_id[5]);
ret = device_register(&sdw_slave->dev);
if (ret) {
dev_err(&mstr->dev, "Register slave failed ret = %d\n", ret);
goto reg_slv_failed;
}
There are some issues with this, as the slave driver only probes when the device actually shows up on the bus. However often (especially in embedded contexts) some things may need to be done to enable the slave. For example it may be held in reset or its power supplies switched off until they are need. As such it generally helps if the device probe can be called before it shows up on the bus, the device probe can then do the necessary actions to enable the device at which point it will show up on the bus.
Thanks, Charles
On 11/14/16 10:08 AM, Charles Keepax wrote:
There are some issues with this, as the slave driver only probes when the device actually shows up on the bus. However often (especially in embedded contexts) some things may need to be done to enable the slave. For example it may be held in reset or its power supplies switched off until they are need. As such it generally helps if the device probe can be called before it shows up on the bus, the device probe can then do the necessary actions to enable the device at which point it will show up on the bus.
Yes, this point was made at the LPC miniconference. What's not clear to me is if you would want the codec driver to be notified that the bus is operational and let it handle things like sideband power management for that device, or is someone else needs to know.
On Mon, Nov 14, 2016 at 11:38:08AM -0600, Pierre-Louis Bossart wrote:
On 11/14/16 10:08 AM, Charles Keepax wrote:
There are some issues with this, as the slave driver only probes when the device actually shows up on the bus. However often (especially in embedded contexts) some things may need to be done to enable the slave. For example it may be held in reset or its power supplies switched off until they are need. As such it generally helps if the device probe can be called before it shows up on the bus, the device probe can then do the necessary actions to enable the device at which point it will show up on the bus.
Yes, this point was made at the LPC miniconference. What's not clear to me is if you would want the codec driver to be notified that the bus is operational and let it handle things like sideband power management for that device, or is someone else needs to know.
I would think it would make sense to provide callbacks that notify the end driver that the device has appeared on/disappears from the bus. Similar to the Qualcomm SLIMBus stuff with the device_up and device_down callbacks. Not all devices will need to use them but they are probably handy to have.
As for actually notifying the end driver that bus itself is operational is the PM runtime stuff going to be sufficient there? As the slaves will be children of the master can't we use that to ensure the bus is always powered when something is in use, and the children themselves can then decide how much power management to apply when they are not in use.
Thanks, Charles
This patch adds support for clock stop prepare, clock stop and clock resume.
As a part of SoundWire clock stop sequence, Master driver needs to call APIs for.
1. Prepare Slaves for Clock Stop. 2. Broadcast ClockStopNow to all Slaves.
As part of SoundWire clock stop resume sequence, Master driver needs to call APIs for.
1. De-prepare Slaves exiting from ClockStop mode 0 2. Optionally de-prepare Slaves exiting from ClockStop mode 1 based on Slave capabilities.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/sdw/sdw.c | 547 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 547 insertions(+)
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c index 801200f..449060e 100644 --- a/sound/sdw/sdw.c +++ b/sound/sdw/sdw.c @@ -671,6 +671,37 @@ static int sdw_transfer(struct sdw_master *mstr, struct sdw_msg *msg, int num, return ret; }
+/* + * NO PM version of Slave transfer. Called from power management APIs + * to avoid dead locks. This is called by bus driver only. + */ +static int sdw_slv_transfer_nopm(struct sdw_master *mstr, + struct sdw_msg *msg, int num) +{ + int ret; + + /* + * If calling from atomic context, return immediately if previous + * message has not completed executing + */ + if (in_atomic() || irqs_disabled()) { + ret = mutex_trylock(&mstr->msg_lock); + if (!ret) { + /* SDW activity is ongoing. */ + ret = -EAGAIN; + goto out; + } + } else { + mutex_lock(&mstr->msg_lock); + } + + ret = sdw_transfer(mstr, msg, num, NULL); + + mutex_unlock(&mstr->lock); +out: + return ret; +} + /** * sdw_bank_switch_deferred: Initiate the transfer of the message but * doesn't wait for the message to be completed. Bus driver waits @@ -1393,6 +1424,189 @@ static void sdw_update_slv_status_event(struct sdw_slave *slave, slv_drv->status_change_event(slave, status); }
+static int sdw_wait_for_clk_stp_deprep(struct sdw_slave *slave, + unsigned int prep_timeout) +{ + int ret; + struct sdw_msg msg; + u8 buf = 0; + int count = 0; + struct sdw_master *mstr = slave->mstr; + + sdw_create_rd_msg(&msg, 0x0, SDW_SCP_STAT, 1, &buf, slave->dev_num); + + /* + * Read the ClockStopNotFinished bit from the SCP_Stat register of + * particular Slave to make sure that clock stop prepare is done + */ + do { + ret = sdw_slv_transfer_nopm(mstr, &msg, SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + WARN_ONCE(1, "Clock stop status read failed\n"); + break; + } + + if (!(buf & SDW_SCP_STAT_CLK_STP_NF_MASK)) { + ret = 0; + break; + } + + usleep_range(1000, 1200); + count++; + + } while (count != prep_timeout); + + if (!(buf & SDW_SCP_STAT_CLK_STP_NF_MASK)) + + dev_info(&mstr->dev, "Clock stop prepare done\n"); + else + WARN_ONCE(1, "Clk stp deprepare failed for slave %d\n", + slave->dev_num); + + return ret; +} + +/* + * This function does one of two things based on "prep" flag. + * 1. Prepare Slave for clock stop, if "prep" flag is true. + * 2. De-prepare Slave after clock resume, if "prep" flag is false. + */ +static void sdw_prepare_slv_for_clk_stp(struct sdw_master *mstr, + struct sdw_slave *slave, + enum sdw_clk_stop_mode clock_stop_mode, + bool prep) +{ + bool wake_en; + struct sdw_slave_caps *cap; + u8 buf = 0; + struct sdw_msg msg; + int ret; + + cap = &slave->priv.caps; + + wake_en = !cap->wake_up_unavailable; + + if (prep) { + /* + * Even if its simplified clock stop prepare, setting + * prepare bit wont harm Here we are not doing write modify + * write since we are updating all fields of SystemCtrl + * registers. Currently highphy is not supported, so + * setting that bit to always 0 + */ + buf |= (1 << SDW_SCP_SYSTEMCTRL_CLK_STP_PREP_SHIFT); + buf |= clock_stop_mode << + SDW_SCP_SYSTEMCTRL_CLK_STP_MODE_SHIFT; + buf |= wake_en << SDW_SCP_SYSTEMCTRL_WAKE_UP_EN_SHIFT; + } else + buf = 0; + + /* + * We are calling NOPM version of the transfer API, because Master + * controllers calls this from the suspend handler, so if we call + * the normal transfer API, it tries to resume controller, which + * results in deadlock + */ + ret = sdw_wr_msg_nopm(&msg, 0x0, SDW_SCP_SYSTEMCTRL, 1, &buf, + slave->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + + /* We should continue even if it fails for some Slave */ + if (ret != SDW_NUM_OF_MSG1_XFRD) + WARN_ONCE(1, "Clock Stop prepare failed for slave %d\n", + slave->dev_num); +} + +/* + * This function checks if the Slave is in "prepared" or "de-prepared" state + * This is used to de-prepare Slaves which are in "prepared" state after + * resuming from ClockStop Mode 1 + */ +static int sdw_check_for_prep_bit(struct sdw_slave *slave) +{ + u8 buf = 0; + struct sdw_msg msg; + int ret; + struct sdw_master *mstr = slave->mstr; + + ret = sdw_rd_msg_nopm(&msg, 0x0, SDW_SCP_SYSTEMCTRL, 1, &buf, + slave->dev_num, mstr, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + dev_err(&mstr->dev, "SCP_SystemCtrl read failed for Slave %d\n", + slave->dev_num); + return -EINVAL; + } + + return !(buf & SDW_SCP_SYSTEMCTRL_CLK_STP_PREP_MASK); +} + +/* + * This function De-prepares particular Slave which is resuming from + * ClockStop mode1. It does following things. + * 1. Check if Slave requires de-prepare based on Slave capabilities. + * 2. Check for the "Prepare" bit in SystemCtrl register. + * 3. If prepare bit is set Deprepare the Slave. + * 4. Wait till Slave is deprepared + */ +static int sdw_deprepare_slv_clk_stp1(struct sdw_slave *slave) +{ + struct sdw_slave_caps *cap; + int ret; + struct sdw_master *mstr = slave->mstr; + struct sdw_slave_priv *slv_priv = &slave->priv; + int prep_timeout = 0; + + cap = &slv_priv->caps; + + /* + * Slave might have enumerated 1st time or from clock stop mode 1 + * return if Slave doesn't require deprepare + */ + if (!cap->clk_stp_prep_hard_reset_behavior) + return 0; + + /* + * If Slave requires de-prepare after exiting from Clock Stop mode + * 1, then check for ClockStopPrepare bit in SystemCtrl register if + * its 1, de-prepare Slave from clock stop prepare, else return + */ + ret = sdw_check_for_prep_bit(slave); + + if (ret < 0) + return ret; + + if (slv_priv->driver->pre_clk_stop_prep) { + ret = slv_priv->driver->pre_clk_stop_prep(slave, + cap->clk_stp1_mode, false); + if (ret < 0) { + dev_warn(&mstr->dev, "Pre de-prepare failed for Slave %d\n", + slave->dev_num); + return ret; + } + } + + prep_timeout = cap->clk_stp_prep_timeout; + sdw_prepare_slv_for_clk_stp(slave->mstr, slave, cap->clk_stp1_mode, + false); + + /* Make sure de-prepare is complete */ + ret = sdw_wait_for_clk_stp_deprep(slave, prep_timeout); + + if (ret < 0) + return ret; + + if (slv_priv->driver->post_clk_stop_prep) { + ret = slv_priv->driver->post_clk_stop_prep(slave, + cap->clk_stp1_mode, false); + + if (ret < 0) + dev_err(&mstr->dev, "Post de-prepare failed for Slave %d ret = %d\n", + slave->dev_num, ret); + } + + return ret; +}
/* * Following thing are done in below loop for each of the registered Slaves. @@ -1470,6 +1684,13 @@ static void sdw_process_slv_status(struct sdw_master *mstr, if (ret < 0) continue;
+ ret = sdw_deprepare_slv_clk_stp1( + mstr->sdw_addr[i].slave); + + if (ret < 0) + continue; + + mstr->sdw_addr[i].status = SDW_SLAVE_STAT_ATTACHED_OK; } @@ -2076,6 +2297,332 @@ void snd_sdw_master_del(struct sdw_master *master) } EXPORT_SYMBOL_GPL(snd_sdw_master_del);
+static enum sdw_clk_stop_mode sdw_slv_get_clk_stp_mode(struct sdw_slave *slave) +{ + enum sdw_clk_stop_mode clock_stop_mode; + struct sdw_slave_priv *slv_priv = &slave->priv; + struct sdw_slave_caps *cap = &slv_priv->caps; + + /* + * Get the dynamic value of clock stop from Slave driver if + * supported, else use the static value from capabilities register. + * Update the capabilities also if we have new dynamic value. + */ + if (slv_priv->driver->get_dyn_clk_stp_mod) { + clock_stop_mode = slv_priv->driver->get_dyn_clk_stp_mod(slave); + if (clock_stop_mode == SDW_CLOCK_STOP_MODE_1) + cap->clk_stp1_mode = true; + else + cap->clk_stp1_mode = false; + } else + clock_stop_mode = cap->clk_stp1_mode; + + return clock_stop_mode; +} + +/** + * snd_sdw_master_stop_clock: Stop the clock. This function broadcasts the + * SCP_CTRL register with clock_stop_now bit set. + * + * @master: Master handle for which clock has to be stopped. + */ +int snd_sdw_master_stop_clock(struct sdw_master *master) +{ + int ret, i; + struct sdw_msg msg; + u8 buf = 0; + enum sdw_clk_stop_mode mode; + + /* + * Send Broadcast message to the SCP_ctrl register with clock stop + * now. If none of the Slaves are attached, then there may not be + * ACK, flag the error about ACK not received but clock will be + * still stopped. + */ + + buf |= 0x1 << SDW_SCP_CTRL_CLK_STP_NOW_SHIFT; + ret = sdw_wr_msg_nopm(&msg, 0x0, SDW_SCP_CTRL, 1, &buf, + SDW_SLAVE_BDCAST_ADDR, master, + SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) + dev_err(&master->dev, "ClockStopNow Broadcast message failed\n"); + + /* + * Mark all Slaves as un-attached which are entering clock stop + * mode1 + */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + + if (!master->sdw_addr[i].assigned) + continue; + + /* Get clock stop mode for all Slaves */ + mode = sdw_slv_get_clk_stp_mode(master->sdw_addr[i].slave); + if (mode == SDW_CLOCK_STOP_MODE_0) + continue; + + /* If clock stop mode 1, mark Slave as not present */ + master->sdw_addr[i].status = SDW_SLAVE_STAT_NOT_PRESENT; + } + return 0; +} +EXPORT_SYMBOL_GPL(snd_sdw_master_stop_clock); + +static struct sdw_slave *sdw_get_slv_status(struct sdw_master *mstr, + int *slave_index) +{ + int i; + + for (i = *slave_index; i <= SDW_MAX_DEVICES; i++) { + if (mstr->sdw_addr[i].assigned != true) + continue; + + if (mstr->sdw_addr[i].status == SDW_SLAVE_STAT_NOT_PRESENT) + continue; + + *slave_index = i + 1; + return mstr->sdw_addr[i].slave; + } + return NULL; +} + +/* + * Wait till ClockStop prepared/De-prepared is finished, Broadcasts the read + * message to read the SCP_STAT register. Wait till ClockStop_NotFinished bit + * is set. Break loop after timeout. + */ +static void sdw_wait_for_clk_stp_prep(struct sdw_master *mstr, unsigned int + prep_timeout) +{ + int ret; + struct sdw_msg msg; + u8 buf = 0; + int count = 0; + + /* Create message to read clock stop status, its broadcast message. */ + sdw_create_rd_msg(&msg, 0x0, SDW_SCP_STAT, 1, &buf, + SDW_SLAVE_BDCAST_ADDR); + /* + * Once all the Slaves are written with prepare bit, broadcast the + * read message for the SCP_STAT register to read the + * ClockStopNotFinished bit. Read till we get this a 0. Currently + * we have timeout of 1sec before giving up. Even if its not read as + * 0 after timeout, controller can stop the clock after warning. + */ + do { + ret = sdw_slv_transfer_nopm(mstr, &msg, SDW_NUM_OF_MSG1_XFRD); + if (ret != SDW_NUM_OF_MSG1_XFRD) { + WARN_ONCE(1, "Clock stop status read failed\n"); + break; + } + + if (!(buf & SDW_SCP_STAT_CLK_STP_NF_MASK)) + break; + + /* + * Sleep in range of 1ms for the max number of millisecond + * of timeout + */ + usleep_range(1000, 1200); + count++; + + } while (count != prep_timeout); + + if (!(buf & SDW_SCP_STAT_CLK_STP_NF_MASK)) + dev_info(&mstr->dev, "Clock stop prepare done\n"); + else + WARN_ONCE(1, "Some Slaves prepare un-successful\n"); +} + +/** + * snd_sdw_master_prepare_for_clk_stop: Prepare all the Slaves for clock + * stop. Iterate through each of the enumerated Slaves. Prepare each + * Slave according to the clock stop mode supported by Slave. Use + * dynamic value from Slave callback if registered, else use static + * values from Slave capabilities registered. + * 1. Get clock stop mode for each Slave. + * 2. Call pre_prepare callback of each Slave if registered. + * 3. Write ClockStopPrepare bit in SCP_SystemCtrl register for each of + * the enumerated Slaves. + * 4. Broadcast the read message to read the SCP_Stat register to make + * sure ClockStop Prepare is finished for all Slaves. + * 5. Call post_prepare callback of each Slave if registered after + * Slaves are in ClockStopPrepare state. + * + * @master: Master handle for which clock state has to be changed. + */ +int snd_sdw_master_prepare_for_clk_stop(struct sdw_master *master) +{ + struct sdw_slave_caps *cap; + enum sdw_clk_stop_mode clock_stop_mode; + int ret; + struct sdw_slave *slave = NULL; + int slv_index = 1; + unsigned int prep_timeout = 0; + + /* + * Get all the Slaves registered to the Master driver for preparing + * for clock stop. Start from Slave with logical address as 1. + */ + while ((slave = sdw_get_slv_status(master, &slv_index)) != NULL) { + + struct sdw_slave_priv *slv_priv = &slave->priv; + + cap = &slv_priv->caps; + + clock_stop_mode = sdw_slv_get_clk_stp_mode(slave); + + /* + * Call the pre clock stop prepare, if Slave requires. + */ + if (slv_priv->driver->pre_clk_stop_prep) { + ret = slv_priv->driver->pre_clk_stop_prep(slave, + clock_stop_mode, true); + + /* If it fails we still continue */ + if (ret < 0) + dev_warn(&master->dev, "Pre prepare failed for Slave %d\n", + slave->dev_num); + } + + sdw_prepare_slv_for_clk_stp(master, slave, clock_stop_mode, + true); + + if (prep_timeout > cap->clk_stp_prep_timeout) + prep_timeout = cap->clk_stp_prep_timeout; + } + + /* Wait till prepare for all Slaves is finished */ + sdw_wait_for_clk_stp_prep(master, prep_timeout); + + slv_index = 1; + while ((slave = sdw_get_slv_status(master, &slv_index)) != NULL) { + + struct sdw_slave_priv *slv_priv = &slave->priv; + + cap = &slv_priv->caps; + + clock_stop_mode = sdw_slv_get_clk_stp_mode(slave); + + if (slv_priv->driver->post_clk_stop_prep) { + ret = slv_priv->driver->post_clk_stop_prep(slave, + clock_stop_mode, + true); + /* + * Even if Slave fails we continue with other + * Slaves. This should never happen ideally. + */ + if (ret < 0) + dev_err(&master->dev, "Post prepare failed for Slave %d ret = %d\n", + slave->dev_num, ret); + + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sdw_master_prepare_for_clk_stop); + +/** + * snd_sdw_master_deprepare_after_clk_start: De-prepare all the Slaves + * exiting clock stop mode 0 after clock resumes. Clock is already + * resumed before this. De-prepare for the Slaves which were there in + * clock stop mode 1 is done after they enumerated back. This is because + * Slave specific callbacks needs to be invoked as part of de-prepare, + * which can be invoked only after Slave enumerates. + * 1. Get clock stop mode for each Slave. + * 2. Call pre_prepare callback of each Slave exiting from clock stop + * mode 0. + * 3. De-Prepare each Slave exiting from clock stop mode 0 + * 4. Broadcast the Read message to make sure all Slaves are + * de-prepared for clock stop. + * 5. Call post_prepare callback of each Slave exiting from clock stop + * mode0 + * + * @master: Master handle + */ +int snd_sdw_master_deprepare_after_clk_start(struct sdw_master *master) +{ + struct sdw_slave_caps *cap; + enum sdw_clk_stop_mode clock_stop_mode; + int ret = 0; + struct sdw_slave *slave = NULL; + bool stop = false; + int slv_index = 1; + unsigned int prep_timeout = 0; + + while ((slave = sdw_get_slv_status(master, &slv_index)) != NULL) { + struct sdw_slave_priv *slv_priv = &slave->priv; + + cap = &slv_priv->caps; + + /* Get the clock stop mode from which Slave is exiting */ + clock_stop_mode = sdw_slv_get_clk_stp_mode(slave); + + /* + * Slave is exiting from Clock stop mode 1, De-prepare is + * optional based on capability, and it has to be done after + * Slave is enumerated. So nothing to be done here. + */ + if (clock_stop_mode == SDW_CLOCK_STOP_MODE_1) + continue; + /* + * Call the pre clock stop prepare, if Slave requires. + */ + if (slv_priv->driver->pre_clk_stop_prep) + ret = slv_priv->driver->pre_clk_stop_prep(slave, + clock_stop_mode, false); + + /* If it fails we still continue */ + if (ret < 0) + dev_warn(&master->dev, "Pre de-prepare failed for Slave %d ret = %d\n", + slave->dev_num, ret); + + sdw_prepare_slv_for_clk_stp(master, slave, clock_stop_mode, + false); + if (prep_timeout > cap->clk_stp_prep_timeout) + prep_timeout = cap->clk_stp_prep_timeout; + } + + /* + * Wait till de-prepare is finished for all the Slaves. + */ + sdw_wait_for_clk_stp_prep(master, prep_timeout); + + slv_index = 1; + while ((slave = sdw_get_slv_status(master, &slv_index)) != NULL) { + + struct sdw_slave_priv *slv_priv = &slave->priv; + + cap = &slv_priv->caps; + + clock_stop_mode = sdw_slv_get_clk_stp_mode(slave); + + /* + * Slave is exiting from Clock stop mode 1, De-prepare is + * optional based on capability, and it has to be done after + * Slave is enumerated. + */ + if (clock_stop_mode == SDW_CLOCK_STOP_MODE_1) + continue; + + if (slv_priv->driver->post_clk_stop_prep) + ret = slv_priv->driver->post_clk_stop_prep(slave, + clock_stop_mode, + stop); + /* + * Even if Slave fails we continue with other + * Slaves. This should never happen ideally. + */ + if (ret < 0) + dev_err(&master->dev, "Post de-prepare failed for Slave %d ret = %d\n", + slave->dev_num, ret); + } + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sdw_master_deprepare_after_clk_start); + /** * snd_sdw_master_get: Return the Master handle from Master number. * Increments the reference count of the module. Similar to
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- include/sound/sdw_bus.h | 7 ++ include/trace/events/sdw.h | 209 ++++++++++++++++++++++++++++++++++++++++++++ sound/sdw/sdw.c | 37 ++++++++ 3 files changed, 253 insertions(+) create mode 100644 include/trace/events/sdw.h
diff --git a/include/sound/sdw_bus.h b/include/sound/sdw_bus.h index 3ea0c71..ad3586e 100644 --- a/include/sound/sdw_bus.h +++ b/include/sound/sdw_bus.h @@ -894,5 +894,12 @@ int snd_sdw_config_ports(struct sdw_master *mstr, struct sdw_slave *slave, */ int snd_sdw_slave_transfer(struct sdw_master *master, struct sdw_msg *msg, unsigned int num); + +/* Function to enable tracing */ +void sdw_transfer_trace_reg(void); + +/* Function to disable tracing */ +void sdw_transfer_trace_unreg(void); + #endif /* _LINUX_SDW_BUS_H */
diff --git a/include/trace/events/sdw.h b/include/trace/events/sdw.h new file mode 100644 index 0000000..447b86e --- /dev/null +++ b/include/trace/events/sdw.h @@ -0,0 +1,209 @@ +/* + * sdw.h - SDW message transfer tracepoints. + * + * Author: Hardik Shah hardik.t.shah@intel.com + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This file is provided under a dual BSD/GPLv2 license. When using or + * redistributing this file, you may do so under either license. + * + * GPL LICENSE SUMMARY + * + * Copyright(c) 2016 Intel Corporation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * + * BSD LICENSE + * + * Copyright(c) 2016 Intel Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM sdw + +#if !defined(_TRACE_SDW_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_SDW_H + +#include <linux/mod_devicetable.h> +#include <sound/sdw_bus.h> +#include <linux/tracepoint.h> + +/* + * __sdw_transfer() write request + */ +TRACE_EVENT_FN(sdw_write, + TP_PROTO(const struct sdw_master *mstr, + const struct sdw_msg *msg, + int num), + TP_ARGS(mstr, msg, num), + TP_STRUCT__entry( + __field(int, master_nr) + __field(__u16, msg_nr) + __field(__u8, addr_page1) + __field(__u8, addr_page2) + __field(__u16, addr) + __field(__u16, flag) + __field(__u16, len) + __dynamic_array(__u8, buf, msg->len)), + TP_fast_assign( + __entry->master_nr = mstr->nr; + __entry->msg_nr = num; + __entry->addr = msg->addr; + __entry->flag = msg->r_w_flag; + __entry->len = msg->len; + __entry->addr_page1 = msg->addr_page1; + __entry->addr_page2 = msg->addr_page2; + memcpy(__get_dynamic_array(buf), msg->buf, msg->len); + ), + TP_printk("sdw-%d #%u a=%03x addr_page1=%04x addr_page2=%04x f=%04x l=%u [%*phD]", + __entry->master_nr, + __entry->msg_nr, + __entry->addr, + __entry->addr_page1, + __entry->addr_page2, + __entry->flag, + __entry->len, + __entry->len, __get_dynamic_array(buf) + ), + sdw_transfer_trace_reg, + sdw_transfer_trace_unreg); + +/* + * __sdw_transfer() read request + */ +TRACE_EVENT_FN(sdw_read, + TP_PROTO(const struct sdw_master *mstr, const struct sdw_msg *msg, + int num), + TP_ARGS(mstr, msg, num), + TP_STRUCT__entry( + __field(int, master_nr) + __field(__u16, msg_nr) + __field(__u8, addr_page1) + __field(__u8, addr_page2) + __field(__u16, addr) + __field(__u16, flag) + __field(__u16, len) + __dynamic_array(__u8, buf, msg->len)), + TP_fast_assign( + __entry->master_nr = mstr->nr; + __entry->msg_nr = num; + __entry->addr = msg->addr; + __entry->flag = msg->r_w_flag; + __entry->len = msg->len; + __entry->addr_page1 = msg->addr_page1; + __entry->addr_page2 = msg->addr_page2; + memcpy(__get_dynamic_array(buf), msg->buf, msg->len); + ), + TP_printk("sdw-%d #%u a=%03x addr_page1=%04x addr_page2=%04x f=%04x l=%u [%*phD]", + __entry->master_nr, + __entry->msg_nr, + __entry->addr, + __entry->addr_page1, + __entry->addr_page2, + __entry->flag, + __entry->len, + __entry->len, __get_dynamic_array(buf) + ), + sdw_transfer_trace_reg, + sdw_transfer_trace_unreg); + +/* + * __sdw_transfer() read reply + */ +TRACE_EVENT_FN(sdw_reply, + TP_PROTO(const struct sdw_master *mstr, + const struct sdw_msg *msg, + int num), + TP_ARGS(mstr, msg, num), + TP_STRUCT__entry( + __field(int, master_nr) + __field(__u16, msg_nr) + __field(__u16, addr) + __field(__u16, flag) + __field(__u16, len) + __dynamic_array(__u8, buf, msg->len)), + TP_fast_assign( + __entry->master_nr = mstr->nr; + __entry->msg_nr = num; + __entry->addr = msg->addr; + __entry->flag = msg->r_w_flag; + __entry->len = msg->len; + memcpy(__get_dynamic_array(buf), msg->buf, msg->len); + ), + TP_printk("sdw-%d #%u a=%03x f=%04x l=%u [%*phD]", + __entry->master_nr, + __entry->msg_nr, + __entry->addr, + __entry->flag, + __entry->len, + __entry->len, __get_dynamic_array(buf) + ), + sdw_transfer_trace_reg, + sdw_transfer_trace_unreg); + +/* + * __sdw_transfer() result + */ +TRACE_EVENT_FN(sdw_result, + TP_PROTO(const struct sdw_master *mstr, int num, int ret), + TP_ARGS(mstr, num, ret), + TP_STRUCT__entry( + __field(int, master_nr) + __field(__u16, nr_msgs) + __field(__s16, ret) + ), + TP_fast_assign( + __entry->master_nr = mstr->nr; + __entry->nr_msgs = num; + __entry->ret = ret; + ), + TP_printk("sdw-%d n=%u ret=%d", + __entry->master_nr, + __entry->nr_msgs, + __entry->ret + ), + sdw_transfer_trace_reg, + sdw_transfer_trace_unreg); + +#endif /* _TRACE_SDW_H */ + +/* This part must be outside protection */ +#include <trace/define_trace.h> diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c index 449060e..71d2550 100644 --- a/sound/sdw/sdw.c +++ b/sound/sdw/sdw.c @@ -70,6 +70,8 @@
#include "sdw_priv.h"
+#define CREATE_TRACE_POINTS +#include <trace/events/sdw.h> /* * Global SoundWire core instance contains list of Masters registered, core * lock and SoundWire stream tags. @@ -335,6 +337,17 @@ static int sdw_match(struct device *dev, struct device_driver *driver) .match = sdw_match, .pm = &soundwire_pm, }; +static struct static_key sdw_trace_msg = STATIC_KEY_INIT_FALSE; + +void sdw_transfer_trace_reg(void) +{ + static_key_slow_inc(&sdw_trace_msg); +} + +void sdw_transfer_trace_unreg(void) +{ + static_key_slow_dec(&sdw_trace_msg); +}
static int sdw_find_free_dev_num(struct sdw_master *mstr, struct sdw_msg *msg) @@ -601,6 +614,21 @@ static int sdw_transfer(struct sdw_master *mstr, struct sdw_msg *msg, int num, u8 prev_adr_pg1 = 0; u8 prev_adr_pg2 = 0;
+ /* + * sdw_trace_msg gets enabled when trace point sdw_slave_transfer gets + * enabled. This is an efficient way of keeping the for-loop from + * being executed when not needed. + */ + if (static_key_false(&sdw_trace_msg)) { + int j; + + for (j = 0; j < num; j++) + if (msg[j].r_w_flag & SDW_MSG_FLAG_READ) + trace_sdw_read(mstr, &msg[j], j); + else + trace_sdw_write(mstr, &msg[j], j); + } + for (i = 0; i < num; i++) {
/* Reset timeout for every message */ @@ -665,6 +693,15 @@ static int sdw_transfer(struct sdw_master *mstr, struct sdw_msg *msg, int num, } }
+ if (static_key_false(&sdw_trace_msg)) { + int j; + + for (j = 0; j < msg->len; j++) + if (msg[j].r_w_flag & SDW_MSG_FLAG_READ) + trace_sdw_reply(mstr, &msg[j], j); + trace_sdw_result(mstr, j, ret); + } + if (!ret) return i + 1;
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- drivers/base/regmap/Kconfig | 3 + drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-sdw.c | 240 ++++++++++++++++++++++++++++++++++++++ include/linux/regmap.h | 37 ++++++ 4 files changed, 281 insertions(+) create mode 100644 drivers/base/regmap/regmap-sdw.c
diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index db9d00c3..1d650c1 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -29,3 +29,6 @@ config REGMAP_MMIO
config REGMAP_IRQ bool + +config REGMAP_SDW + depends on SOUND_SDW diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 609e4c8..ee041e0 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o +obj-$(CONFIG_REGMAP_SDW) += regmap-sdw.o diff --git a/drivers/base/regmap/regmap-sdw.c b/drivers/base/regmap/regmap-sdw.c new file mode 100644 index 0000000..d76ee12 --- /dev/null +++ b/drivers/base/regmap/regmap-sdw.c @@ -0,0 +1,240 @@ +/* + * regmap-sdw.c - Register map access API - SoundWire support. + * + * Copyright (C) 2015-2016 Intel Corp + * Author: Hardik Shah hardik.t.shah@intel.com + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + */ + +#include <linux/regmap.h> +#include <sound/sdw_bus.h> +#include <sound/sdw_master.h> +#include <sound/sdw_slave.h> +#include <linux/module.h> +#include <sound/sdw/sdw_registers.h> + +#include "internal.h" + + +static inline void get_t_size(size_t *t_val_size, size_t *t_size, + int *reg_addr, + int *offset, + size_t *val_size) +{ + + *t_val_size += *t_size; + *offset += *t_size; + + *t_size = *val_size - *t_val_size; + *t_size = min_t(size_t, *t_size, 65535); + + *reg_addr += *t_size; + + +} + +static int regmap_sdw_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct device *dev = context; + struct sdw_slave *sdw = to_sdw_slave(dev); + struct sdw_msg xfer; + int ret, scp_addr1, scp_addr2; + int reg_command; + int reg_addr = *(u32 *)reg; + size_t t_val_size = 0, t_size; + int offset; + u8 *t_val; + + /* SoundWire registers are 32-bit addressed */ + if (reg_size != 4) + return -ENOTSUPP; + + xfer.dev_num = sdw->dev_num; + xfer.xmit_on_ssp = 0; + xfer.r_w_flag = SDW_MSG_FLAG_READ; + xfer.len = 0; + t_val = val; + + offset = 0; + reg_command = (reg_addr >> SDW_REGADDR_SHIFT) & + SDW_REGADDR_MASK; + if (val_size > SDW_MAX_REG_ADDR) + t_size = SDW_MAX_REG_ADDR - reg_command; + else + t_size = val_size; + + while (t_val_size < val_size) { + + scp_addr1 = (reg_addr >> SDW_SCP_ADDRPAGE1_SHIFT) & + SDW_SCP_ADDRPAGE1_MASK; + scp_addr2 = (reg_addr >> SDW_SCP_ADDRPAGE2_SHIFT) & + SDW_SCP_ADDRPAGE2_MASK; + xfer.addr_page1 = scp_addr1; + xfer.addr_page2 = scp_addr2; + xfer.addr = reg_command; + xfer.len += t_size; + xfer.buf = &t_val[offset]; + ret = snd_sdw_slave_transfer(sdw->mstr, &xfer, 1); + if (ret < 0) + return ret; + else if (ret != 1) + return -EIO; + + get_t_size(&t_val_size, &t_size, ®_addr, &offset, + &val_size); + + reg_command = (reg_addr >> SDW_REGADDR_SHIFT) & + SDW_REGADDR_MASK; + } + return 0; +} + +static int regmap_sdw_gather_write(void *context, + const void *reg, size_t reg_size, + const void *val, size_t val_size) +{ + struct device *dev = context; + struct sdw_slave *sdw = to_sdw_slave(dev); + struct sdw_msg xfer; + int ret, scp_addr1, scp_addr2; + int reg_command; + int reg_addr = *(u32 *)reg; + size_t t_val_size = 0, t_size; + int offset; + u8 *t_val; + + /* All registers are 4 byte on SoundWire bus */ + if (reg_size != 4) + return -ENOTSUPP; + + if (!sdw) + return 0; + + xfer.dev_num = sdw->dev_num; + xfer.xmit_on_ssp = 0; + xfer.r_w_flag = SDW_MSG_FLAG_WRITE; + xfer.len = 0; + t_val = (u8 *)val; + + offset = 0; + reg_command = (reg_addr >> SDW_REGADDR_SHIFT) & + SDW_REGADDR_MASK; + if (val_size > SDW_MAX_REG_ADDR) + t_size = SDW_MAX_REG_ADDR - reg_command; + else + t_size = val_size; + while (t_val_size < val_size) { + + scp_addr1 = (reg_addr >> SDW_SCP_ADDRPAGE1_SHIFT) & + SDW_SCP_ADDRPAGE1_MASK; + scp_addr2 = (reg_addr >> SDW_SCP_ADDRPAGE2_SHIFT) & + SDW_SCP_ADDRPAGE2_MASK; + xfer.addr_page1 = scp_addr1; + xfer.addr_page2 = scp_addr2; + xfer.addr = reg_command; + xfer.len += t_size; + xfer.buf = &t_val[offset]; + ret = snd_sdw_slave_transfer(sdw->mstr, &xfer, 1); + if (ret < 0) + return ret; + else if (ret != 1) + return -EIO; + + get_t_size(&t_val_size, &t_size, ®_addr, &offset, + &val_size); + + reg_command = (reg_addr >> SDW_REGADDR_SHIFT) & + SDW_REGADDR_MASK; + } + return 0; +} + +static int regmap_sdw_write(void *context, const void *data, size_t count) +{ + /* 4-byte register address for the soundwire */ + unsigned int offset = 4; + + if (count <= offset) + return -EINVAL; + + return regmap_sdw_gather_write(context, data, 4, + data + offset, count - offset); +} + +static struct regmap_bus regmap_sdw = { + .write = regmap_sdw_write, + .gather_write = regmap_sdw_gather_write, + .read = regmap_sdw_read, + .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, +}; + +static int regmap_sdw_config_check(const struct regmap_config *config) +{ + /* All register are 8-bits wide as per MIPI Soundwire 1.0 Spec */ + if (config->val_bits != 8) + return -ENOTSUPP; + /* Registers are 32 bit in size, based on SCP_ADDR1 and SCP_ADDR2 + * implementation address range may vary in slave. + */ + if (config->reg_bits != 32) + return -ENOTSUPP; + /* SoundWire register address are contiguous. */ + if (config->reg_stride != 0) + return -ENOTSUPP; + if (config->pad_bits != 0) + return -ENOTSUPP; + + + return 0; +} + +struct regmap *__regmap_init_sdw(struct sdw_slave *sdw, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + int ret; + + ret = regmap_sdw_config_check(config); + if (ret) + return ERR_PTR(ret); + + return __regmap_init(&sdw->dev, ®map_sdw, &sdw->dev, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__regmap_init_sdw); + + +struct regmap *__devm_regmap_init_sdw(struct sdw_slave *sdw, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + int ret; + + ret = regmap_sdw_config_check(config); + if (ret) + return ERR_PTR(ret); + + return __devm_regmap_init(&sdw->dev, ®map_sdw, &sdw->dev, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__devm_regmap_init_sdw); + +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 9adc7b2..5c192f7 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -29,6 +29,7 @@ struct regmap_range_cfg; struct regmap_field; struct snd_ac97; +struct sdw_slave;
/* An enum of all the supported cache types */ enum regcache_type { @@ -456,6 +457,10 @@ struct regmap *__regmap_init_ac97(struct snd_ac97 *ac97, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__regmap_init_sdw(struct sdw_slave *sdw, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name);
struct regmap *__devm_regmap_init(struct device *dev, const struct regmap_bus *bus, @@ -489,6 +494,10 @@ struct regmap *__devm_regmap_init_ac97(struct snd_ac97 *ac97, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__devm_regmap_init_sdw(struct sdw_slave *sdw, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name);
/* * Wrapper for regmap_init macros to include a unique lockdep key and name @@ -623,6 +632,20 @@ int regmap_attach_dev(struct device *dev, struct regmap *map, bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);
/** + * regmap_init_sdw(): Initialise register map + * + * @sdw: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +#define regmap_init_sdw(sdw, config) \ + __regmap_lockdep_wrapper(__regmap_init_sdw, #config, \ + sdw, config) + + +/** * devm_regmap_init(): Initialise managed register map * * @dev: Device that will be interacted with @@ -737,6 +760,20 @@ int regmap_attach_dev(struct device *dev, struct regmap *map, __regmap_lockdep_wrapper(__devm_regmap_init_ac97, #config, \ ac97, config)
+/** + * devm_regmap_init_sdw(): Initialise managed register map + * + * @sdw: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct regmap. The regmap will be automatically freed by the + * device management code. + */ +#define devm_regmap_init_sdw(sdw, config) \ + __regmap_lockdep_wrapper(__devm_regmap_init_sdw, #config, \ + sdw, config) + void regmap_exit(struct regmap *map); int regmap_reinit_cache(struct regmap *map, const struct regmap_config *config);
On Fri, Oct 21, 2016 at 06:11:10PM +0530, Hardik Shah wrote:
+static inline void get_t_size(size_t *t_val_size, size_t *t_size,
int *reg_addr,
int *offset,
size_t *val_size)
+{
*t_val_size += *t_size;
*offset += *t_size;
So, I started by taking a look at this patch to get a sense of what the API would look like and I see that we're not following CodingStyle with everything indented twice :(
+static int regmap_sdw_gather_write(void *context,
const void *reg, size_t reg_size,
const void *val, size_t val_size)
+{
- if (!sdw)
return 0;
Silently ignoring errors :(
- if (val_size > SDW_MAX_REG_ADDR)
t_size = SDW_MAX_REG_ADDR - reg_command;
- else
t_size = val_size;
This needs at least some kind of comment?
On Fri, Oct 28, 2016 at 07:03:55PM +0100, Mark Brown wrote:
On Fri, Oct 21, 2016 at 06:11:10PM +0530, Hardik Shah wrote:
+static inline void get_t_size(size_t *t_val_size, size_t *t_size,
int *reg_addr,
int *offset,
size_t *val_size)
+{
*t_val_size += *t_size;
*offset += *t_size;
So, I started by taking a look at this patch to get a sense of what the API would look like and I see that we're not following CodingStyle with everything indented twice :(
Sorry Mark for delayed response, there was festival holidays in India, so I was not able to reply.
Regarding two tabs, it got missed for this function somehow, I will fix it in next patchset.
+static int regmap_sdw_gather_write(void *context,
const void *reg, size_t reg_size,
const void *val, size_t val_size)
+{
- if (!sdw)
return 0;
Silently ignoring errors :(
This should never happen ideally. This was extra check to make sure there is not kernel crash. I can remove it as well.
- if (val_size > SDW_MAX_REG_ADDR)
t_size = SDW_MAX_REG_ADDR - reg_command;
- else
t_size = val_size;
This needs at least some kind of comment?
I will add few comments here.
--
From: Sanyog Kale sanyog.r.kale@intel.com
This patch adds APIs for stream and port configurations for SoundWire bus driver.
Signed-off-by: Hardik Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com --- sound/sdw/sdw.c | 774 ++++++++++++++++++++++++++++++++++++++++++++++++++ sound/sdw/sdw_priv.h | 449 +++++++++++++++++++++++++++++ 2 files changed, 1223 insertions(+)
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c index 71d2550..ffbec9e 100644 --- a/sound/sdw/sdw.c +++ b/sound/sdw/sdw.c @@ -2358,6 +2358,780 @@ static enum sdw_clk_stop_mode sdw_slv_get_clk_stp_mode(struct sdw_slave *slave) }
/** + * snd_sdw_release_stream_tag: Free the already assigned stream tag. + * Reverses effect of "sdw_alloc_stream_tag" + * + * @stream_tag: Stream tag to be freed. + */ +void snd_sdw_release_stream_tag(unsigned int stream_tag) +{ + int i; + struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags; + + /* Acquire core lock */ + mutex_lock(&snd_sdw_core.core_mutex); + + /* Get stream tag data structure */ + for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) { + if (stream_tag == stream_tags[i].stream_tag) { + + /* Reference count update */ + sdw_dec_ref_count(&stream_tags[i].ref_count); + + if (stream_tags[i].ref_count == 0) + /* Free up resources */ + kfree(stream_tags[i].sdw_rt); + } + } + + /* Release core lock */ + mutex_unlock(&snd_sdw_core.core_mutex); +} +EXPORT_SYMBOL_GPL(snd_sdw_release_stream_tag); + +/** + * snd_sdw_alloc_stream_tag: Allocates unique stream_tag. Stream tag is + * a unique identifier for each SoundWire stream across all SoundWire + * bus instances. Stream tag is a software concept defined by bus + * driver for stream management and not by MIPI SoundWire Spec. Each + * SoundWire Stream is individually configured and controlled using the + * stream tag. Multiple Master(s) and Slave(s) associated with the + * stream, uses stream tag as an identifier. All the operations on the + * stream e.g. stream configuration, port configuration, prepare and + * enable of the ports are done based on stream tag. This API shall be + * called once per SoundWire stream either by the Master or Slave + * associated with the stream. + * + * @stream_tag: Stream tag returned by bus driver. + */ +int snd_sdw_alloc_stream_tag(unsigned int *stream_tag) +{ + int i; + int ret = -EINVAL; + struct sdw_runtime *sdw_rt; + struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags; + + /* Acquire core lock */ + mutex_lock(&snd_sdw_core.core_mutex); + + /* Allocate new stream tag and initialize resources */ + for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) { + if (!stream_tags[i].ref_count) { + + *stream_tag = stream_tags[i].stream_tag; + + /* Initialize stream lock */ + mutex_init(&stream_tags[i].stream_lock); + + /* Allocate resources for stream runtime handle */ + sdw_rt = kzalloc(sizeof(*sdw_rt), GFP_KERNEL); + if (!sdw_rt) { + ret = -ENOMEM; + goto out; + } + + /* Reference count update */ + sdw_inc_ref_count(&stream_tags[i].ref_count); + + /* Initialize Master and Slave list */ + INIT_LIST_HEAD(&sdw_rt->slv_rt_list); + INIT_LIST_HEAD(&sdw_rt->mstr_rt_list); + + /* Change stream state to ALLOC */ + sdw_rt->stream_state = SDW_STATE_STRM_ALLOC; + + stream_tags[i].sdw_rt = sdw_rt; + + ret = 0; + break; + } + } +out: + /* Release core lock */ + mutex_unlock(&snd_sdw_core.core_mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_sdw_alloc_stream_tag); + +/** + * sdw_config_mstr_stream: Checks if master runtime handle already + * available, if not allocates and initialize Master runtime handle. + * + * @mstr: Master handle + * @stream_config: Stream configuration for the SoundWire audio stream. + * @sdw_rt: Stream runtime handle. + * + * Returns Master runtime handle. + */ +static struct sdw_mstr_runtime *sdw_config_mstr_stream(struct sdw_master *mstr, + struct sdw_stream_config *stream_config, + struct sdw_runtime *sdw_rt) +{ + struct sdw_mstr_runtime *mstr_rt = NULL; + struct sdw_stream_params *str_p; + + /* Retrieve Master handle if already available */ + list_for_each_entry(mstr_rt, &sdw_rt->mstr_rt_list, mstr_strm_node) { + if (mstr_rt->mstr == mstr) + return mstr_rt; + } + + /* Allocate resources for Master runtime handle */ + mstr_rt = kzalloc(sizeof(*mstr_rt), GFP_KERNEL); + if (!mstr_rt) + goto out; + + /* Initialization of Master runtime handle */ + INIT_LIST_HEAD(&mstr_rt->port_rt_list); + INIT_LIST_HEAD(&mstr_rt->slv_rt_list); + list_add_tail(&mstr_rt->mstr_strm_node, &sdw_rt->mstr_rt_list); + list_add_tail(&mstr_rt->mstr_node, &mstr->mstr_rt_list); + + /* Update PCM parameters for Master */ + mstr_rt->direction = stream_config->direction; + str_p = &mstr_rt->stream_params; + str_p->rate = stream_config->frame_rate; + str_p->channel_count = stream_config->channel_count; + str_p->bps = stream_config->bps; + + /* Add reference for Master device handle */ + mstr_rt->mstr = mstr; + + /* Add reference for stream runtime handle */ + mstr_rt->sdw_rt = sdw_rt; + +out: + return mstr_rt; +} + +/** + * sdw_config_slave_stream: Allocate and initialize slave runtime handle. + * + * @slave: Slave handle + * @stream_config: Stream configuration for the SoundWire audio stream. + * @sdw_rt: Stream runtime handle. + * + * Returns Slave runtime handle. + */ +static struct sdw_slv_runtime *sdw_config_slv_stream( + struct sdw_slave *slave, + struct sdw_stream_config *stream_config, + struct sdw_runtime *sdw_rt) +{ + struct sdw_slv_runtime *slv_rt = NULL; + struct sdw_stream_params *str_p; + + /* Allocate resources for Slave runtime handle */ + slv_rt = kzalloc(sizeof(*slv_rt), GFP_KERNEL); + if (!slv_rt) + goto out; + + /* Initialization of Slave runtime handle */ + INIT_LIST_HEAD(&slv_rt->port_rt_list); + + /* Update PCM parameters for Slave */ + slv_rt->direction = stream_config->direction; + str_p = &slv_rt->stream_params; + str_p->rate = stream_config->frame_rate; + str_p->channel_count = stream_config->channel_count; + str_p->bps = stream_config->bps; + + /* Add reference for Slave device handle */ + slv_rt->slv = slave; + + /* Add reference for stream runtime handle */ + slv_rt->sdw_rt = sdw_rt; + +out: + return slv_rt; +} + +/** + * sdw_release_mstr_stream: Removes entry from master runtime list and free + * up resources. + * + * @mstr: Master handle. + * @sdw_rt: Master runtime handle. + */ +static void sdw_release_mstr_stream(struct sdw_master *mstr, + struct sdw_runtime *sdw_rt) +{ + struct sdw_mstr_runtime *mstr_rt, *__mstr_rt; + + /* Retrieve Master runtime handle */ + list_for_each_entry_safe(mstr_rt, __mstr_rt, &sdw_rt->mstr_rt_list, + mstr_strm_node) { + + if (mstr_rt->mstr == mstr) { + + if (mstr_rt->direction == SDW_DATA_DIR_OUT) + /* Reference count update */ + sdw_dec_ref_count(&sdw_rt->tx_ref_count); + else + /* Reference count update */ + sdw_dec_ref_count(&sdw_rt->rx_ref_count); + + /* Remove node from the list */ + list_del(&mstr_rt->mstr_strm_node); + list_del(&mstr_rt->mstr_node); + + pm_runtime_mark_last_busy(&mstr->dev); + pm_runtime_put_sync_autosuspend(&mstr->dev); + + /* Free up Master runtime handle resources */ + kfree(mstr_rt); + } + } +} + +/** + * sdw_release_slv_stream: Removes entry from slave runtime list and free up + * resources. + * + * @slave: Slave handle. + * @sdw_rt: Stream runtime handle. + */ +static void sdw_release_slv_stream(struct sdw_slave *slave, + struct sdw_runtime *sdw_rt) +{ + struct sdw_slv_runtime *slv_rt, *__slv_rt; + + /* Retrieve Slave runtime handle */ + list_for_each_entry_safe(slv_rt, __slv_rt, &sdw_rt->slv_rt_list, + slave_strm_node) { + + if (slv_rt->slv == slave) { + + if (slv_rt->direction == SDW_DATA_DIR_OUT) + /* Reference count update */ + sdw_dec_ref_count(&sdw_rt->tx_ref_count); + else + /* Reference count update */ + sdw_dec_ref_count(&sdw_rt->rx_ref_count); + + /* Remove node from the list */ + list_del(&slv_rt->slave_strm_node); + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_sync_autosuspend(&slave->dev); + + /* Free up Slave runtime handle resources */ + kfree(slv_rt); + } + } +} + +/** + * snd_sdw_release_stream: De-associates Master(s) and Slave(s) from stream. + * Reverse effect of the sdw_config_stream. Master calls this with + * Slave handle as NULL, Slave calls this with Master handle as NULL. + * + * @mstr: Master handle, + * @slave: SoundWire Slave handle, Null if stream configuration is called by + * Master driver. + * + * @stream_tag: Stream_tag representing the audio stream. All Masters and + * Slaves part of the same stream has same stream tag. So Bus driver + * holds information of all Masters and Slaves associated with stream + * tag. + */ +int snd_sdw_release_stream(struct sdw_master *mstr, + struct sdw_slave *slave, + unsigned int stream_tag) +{ + int i; + struct sdw_runtime *sdw_rt = NULL; + struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags; + + /* Retrieve master handle if called by Slave */ + if (!mstr) + mstr = slave->mstr; + + /* Retrieve stream runtime handle */ + for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) { + if (stream_tags[i].stream_tag == stream_tag) { + sdw_rt = stream_tags[i].sdw_rt; + break; + } + } + + if (!sdw_rt) { + dev_err(&mstr->dev, "Invalid stream tag\n"); + return -EINVAL; + } + + /* Call release API of Master/Slave */ + if (!slave) + sdw_release_mstr_stream(mstr, sdw_rt); + else + sdw_release_slv_stream(slave, sdw_rt); + + return 0; +} +EXPORT_SYMBOL_GPL(snd_sdw_release_stream); + +/** + * snd_sdw_config_stream: Configures the SoundWire stream. All the Master(s) + * and Slave(s) associated with the stream calls this API with + * "sdw_stream_config". This API configures SoundWire stream based on + * "sdw_stream_config" provided by each Master(s) and Slave(s) + * associated with the stream. Master calls this function with Slave + * handle as NULL, Slave calls this with Master handle as NULL. + * + * @mstr: Master handle. + * @slave: SoundWire Slave handle, Null if stream configuration is called by + * Master driver. + * + * @stream_config: Stream configuration for the SoundWire audio stream. + * @stream_tag: Stream_tag representing the audio stream. All Masters and + * Slaves part of the same stream has same stream tag. So Bus driver + * holds information of all Masters and Slaves associated with stream + * tag. + */ +int snd_sdw_config_stream(struct sdw_master *mstr, + struct sdw_slave *slave, + struct sdw_stream_config *stream_config, + unsigned int stream_tag) +{ + int i; + int ret = 0; + struct sdw_runtime *sdw_rt = NULL; + struct sdw_mstr_runtime *mstr_rt = NULL; + struct sdw_slv_runtime *slv_rt = NULL; + struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags; + struct sdw_stream_tag *stream = NULL; + + /* Retrieve master handle if called by Slave */ + if (!mstr) + mstr = slave->mstr; + + /* Retrieve stream runtime handle */ + for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) { + if (stream_tags[i].stream_tag == stream_tag) { + sdw_rt = stream_tags[i].sdw_rt; + stream = &stream_tags[i]; + break; + } + } + + if (!sdw_rt) { + dev_err(&mstr->dev, "Valid stream tag not found\n"); + ret = -EINVAL; + goto out; + } + + /* Acquire stream lock */ + mutex_lock(&stream->stream_lock); + + /* Get and Initialize Master runtime handle */ + mstr_rt = sdw_config_mstr_stream(mstr, stream_config, sdw_rt); + if (!mstr_rt) { + dev_err(&mstr->dev, "Master runtime configuration failed\n"); + ret = -EINVAL; + goto error; + } + + /* Initialize Slave runtime handle */ + if (slave) { + slv_rt = sdw_config_slv_stream(slave, stream_config, sdw_rt); + if (!slv_rt) { + dev_err(&mstr->dev, "Slave runtime configuration failed\n"); + ret = -EINVAL; + goto error; + } + } + + /* + * Stream params will be stored based on Tx only, since there can be + * only one Tx and multiple Rx, There can be multiple Tx if there is + * aggregation on Tx. That is handled by adding the channels to + * stream_params for each aggregated Tx slaves + */ + if (!sdw_rt->tx_ref_count && stream_config->direction == + SDW_DATA_DIR_OUT) { + sdw_rt->stream_params.rate = stream_config->frame_rate; + sdw_rt->stream_params.channel_count = + stream_config->channel_count; + sdw_rt->stream_params.bps = stream_config->bps; + /* Reference count update */ + sdw_inc_ref_count(&sdw_rt->tx_ref_count); + } + + /* + * Normally there will be only one Tx in system, multiple Tx can + * only be there if we support aggregation. In that case there may + * be multiple slave or masters handing different channels of same + * Tx stream. + */ + else if (sdw_rt->tx_ref_count && stream_config->direction == + SDW_DATA_DIR_OUT) { + if (sdw_rt->stream_params.rate != + stream_config->frame_rate) { + dev_err(&mstr->dev, "Frame rate for aggregated devices not matching\n"); + ret = -EINVAL; + goto error; + } + + if (sdw_rt->stream_params.bps != stream_config->bps) { + dev_err(&mstr->dev, "bps for aggregated devices not matching\n"); + ret = -EINVAL; + goto error; + } + + /* + * Number of channels gets added, since both devices will be + * supporting different channels. Like one Codec supporting + * L and other supporting R channel. + */ + sdw_rt->stream_params.channel_count += + stream_config->channel_count; + + /* Reference count update */ + sdw_inc_ref_count(&sdw_rt->tx_ref_count); + } else + /* Reference count update */ + sdw_inc_ref_count(&sdw_rt->rx_ref_count); + + sdw_rt->type = stream_config->type; + + /* Change stream state to CONFIG */ + sdw_rt->stream_state = SDW_STATE_STRM_CONFIG; + + /* + * Slaves are added to two list, This is because bandwidth is + * calculated for two masters individually, while Ports are enabled + * of all the aggregated masters and slaves part of the same stream + * tag simultaneously. + */ + if (slave) { + list_add_tail(&slv_rt->slave_strm_node, &sdw_rt->slv_rt_list); + list_add_tail(&slv_rt->slave_mstr_node, &mstr_rt->slv_rt_list); + } + + /* Release stream lock */ + mutex_unlock(&stream->stream_lock); + + if (slave) + pm_runtime_get_sync(&slave->dev); + else + pm_runtime_get_sync(&mstr->dev); + + return ret; + +error: + mutex_unlock(&stream->stream_lock); + kfree(mstr_rt); + kfree(slv_rt); +out: + return ret; + +} +EXPORT_SYMBOL_GPL(snd_sdw_config_stream); + +/** + * sdw_check_dpn_caps: Check Master and Slave port capabilities. This performs + * PCM parameter check based on PCM + * parameters received in stream. + * @dpn_cap: Capabilities of Master or Slave port. + * @strm_params: Stream PCM parameters. + */ +static int sdw_check_dpn_caps(struct sdw_dpn_caps *dpn_cap, + struct sdw_stream_params *strm_prms) +{ + struct sdw_port_aud_mode_prop *mode_prop = + dpn_cap->mode_properties; + int i, value; + + /* Check for sampling frequency */ + if (mode_prop->num_sample_rate_cfgs) { + for (i = 0; i < mode_prop->num_sample_rate_cfgs; i++) { + value = mode_prop->sample_rate_buf[i]; + if (strm_prms->rate == value) + break; + } + + if (i == mode_prop->num_sample_rate_cfgs) + return -EINVAL; + } else { + + if ((strm_prms->rate < mode_prop->min_sample_rate) + || (strm_prms->rate > + mode_prop->max_sample_rate)) { + return -EINVAL; + } + } + + /* Check for bit rate */ + if (dpn_cap->num_bps) { + for (i = 0; i < dpn_cap->num_bps; i++) { + value = dpn_cap->bps_buf[i]; + if (strm_prms->bps == value) + break; + } + + if (i == dpn_cap->num_bps) + return -EINVAL; + + } else { + + if ((strm_prms->bps < dpn_cap->min_bps) + || (strm_prms->bps > dpn_cap->max_bps)) + return -EINVAL; + } + + /* Check for number of channels */ + if (dpn_cap->num_ch_cnt) { + for (i = 0; i < dpn_cap->num_ch_cnt; i++) { + value = dpn_cap->ch_cnt_buf[i]; + if (strm_prms->bps == value) + break; + } + + if (i == dpn_cap->num_ch_cnt) + return -EINVAL; + + } else { + + if ((strm_prms->channel_count < dpn_cap->min_ch_cnt) || + (strm_prms->channel_count > dpn_cap->max_ch_cnt)) + return -EINVAL; + + } + + return 0; +} + +/** + * sdw_mstr_port_configuration: Master Port configuration. This performs + * all the port related configuration including allocation port + * structure memory, assign PCM parameters and add port node in master + * runtime list. + * + * @mstr: Master handle. + * @sdw_rt: Stream runtime information. + * @ports_config: Port configuration for Slave. + */ +static int sdw_mstr_port_configuration(struct sdw_master *mstr, + struct sdw_runtime *sdw_rt, + struct sdw_ports_config *ports_config) +{ + struct sdw_mstr_runtime *mstr_rt = NULL; + struct sdw_port_runtime *port_rt; + int found = 0; + int i; + int ret = 0, pn = 0; + struct sdw_dpn_caps *dpn_cap = mstr->caps.sdw_dpn_caps; + + /* Get Master device handle */ + list_for_each_entry(mstr_rt, &sdw_rt->mstr_rt_list, mstr_strm_node) { + if (mstr_rt->mstr == mstr) { + found = 1; + break; + } + } + + if (!found) { + dev_err(&mstr->dev, "Master not found for this port\n"); + return -EINVAL; + } + + /* Allocate resources for port runtime handle */ + port_rt = kzalloc((sizeof(*port_rt) * ports_config->num_ports), + GFP_KERNEL); + if (!port_rt) + return -ENOMEM; + + /* Check master capabilities */ + if (!dpn_cap) + return -EINVAL; + + /* Iterate for number of ports to perform initialization */ + for (i = 0; i < ports_config->num_ports; i++) { + port_rt[i].channel_mask = ports_config->port_config[i].ch_mask; + port_rt[i].port_num = pn = ports_config->port_config[i].num; + + /* Perform capability check for master port */ + ret = sdw_check_dpn_caps(&dpn_cap[pn], &mstr_rt->stream_params); + if (ret < 0) { + dev_err(&mstr->dev, "Master capabilities check failed ret = %d\n", ret); + goto error; + } + + /* Add node to port runtime list */ + list_add_tail(&port_rt[i].port_node, &mstr_rt->port_rt_list); + } + + return ret; + +error: + kfree(port_rt); + return ret; +} + +struct sdw_dpn_caps *sdw_get_slv_dpn_cap(struct sdw_slave_caps *slv_cap, + enum sdw_data_direction direction, + unsigned int port_num) +{ + int i; + struct sdw_dpn_caps *dpn_cap; + u8 num_ports; + bool port_found = 0; + + if (direction == SDW_DATA_DIR_OUT) + num_ports = slv_cap->num_src_ports; + else + num_ports = slv_cap->num_sink_ports; + + for (i = 0; i < num_ports; i++) { + dpn_cap = &slv_cap->dpn_caps[direction][i]; + + if (dpn_cap->port_number == port_num) { + port_found = 1; + break; + } + } + + if (!port_found) + return NULL; + + return dpn_cap; + +} + +/** + * sdw_config_slv_port: Slave Port configuration. This performs + * all the port related configuration including allocation port + * structure memory, assign PCM parameters and add port node in slave + * runtime list. + * @slave: Slave handle. + * @sdw_rt: Stream runtime information. + * @ports_config: Port configuration for Slave. + */ +static int sdw_config_slv_port(struct sdw_slave *slave, + struct sdw_runtime *sdw_rt, + struct sdw_ports_config *ports_config) +{ + struct sdw_slv_runtime *slv_rt; + struct sdw_port_runtime *port_rt; + struct sdw_dpn_caps *dpn_cap; + int found = 0, ret = 0; + int i, pn; + + /* Get Slave device handle */ + list_for_each_entry(slv_rt, &sdw_rt->slv_rt_list, slave_strm_node) { + if (slv_rt->slv == slave) { + found = 1; + break; + } + } + + if (!found) { + dev_err(&slave->mstr->dev, "Slave not found for this port\n"); + return -EINVAL; + } + + /* Check whether slave capabilities are valid or invalid */ + if (!slave->priv.slave_cap_updated) { + dev_err(&slave->mstr->dev, "Slave capabilities not updated\n"); + return -EINVAL; + } + + /* Allocate resources for port runtime handle */ + port_rt = kzalloc((sizeof(*port_rt) * ports_config->num_ports), + GFP_KERNEL); + if (!port_rt) + return -ENOMEM; + + /* Assign PCM parameters */ + for (i = 0; i < ports_config->num_ports; i++) { + port_rt[i].channel_mask = ports_config->port_config[i].ch_mask; + port_rt[i].port_num = pn = + ports_config->port_config[i].num; + + dpn_cap = sdw_get_slv_dpn_cap(&slave->priv.caps, + slv_rt->direction, + ports_config->port_config[i].num); + if (!dpn_cap) { + ret = -EINVAL; + dev_err(&slave->mstr->dev, "Slave port capabilities not found ret = %d\n", ret); + goto error; + } + + /* Perform capability check for slave port */ + ret = sdw_check_dpn_caps(dpn_cap, &slv_rt->stream_params); + if (ret < 0) { + dev_err(&slave->mstr->dev, "Slave capabilities check failed ret = %d\n", ret); + goto error; + } + + /* Add node to port runtime list */ + list_add_tail(&port_rt[i].port_node, &slv_rt->port_rt_list); + } + + return ret; + +error: + kfree(port_rt); + return ret; +} + +/** + * snd_sdw_config_ports: Configures Master or Slave Port(s) associated with + * the stream. All the Master(s) and Slave(s) associated with the + * stream calls this API with "sdw_ports_config". Master calls this + * function with Slave handle as NULL, Slave calls this with Master + * handle as NULL. + * + * @mstr: Master handle where the Slave is connected. + * @slave: Slave handle. + * @ports_config: Port configuration for each Port of SoundWire Slave. + * @stream_tag: Stream tag, where this Port is connected. + */ +int snd_sdw_config_ports(struct sdw_master *mstr, struct sdw_slave *slave, + struct sdw_ports_config *ports_config, + unsigned int stream_tag) +{ + int ret; + int i; + struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags; + struct sdw_runtime *sdw_rt = NULL; + struct sdw_stream_tag *stream = NULL; + + /* Retrieve master handle if called by Slave */ + if (!mstr) + mstr = slave->mstr; + + /* Retrieve stream runtime handle */ + for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) { + if (stream_tags[i].stream_tag == stream_tag) { + sdw_rt = stream_tags[i].sdw_rt; + stream = &stream_tags[i]; + break; + } + } + + if (!sdw_rt) { + dev_err(&mstr->dev, "Invalid stream tag\n"); + return -EINVAL; + } + + /* Acquire stream lock */ + mutex_lock(&stream->stream_lock); + + /* Perform Master/Slave port configuration */ + if (!slave) + ret = sdw_mstr_port_configuration(mstr, sdw_rt, ports_config); + else + ret = sdw_config_slv_port(slave, sdw_rt, ports_config); + + /* Release stream lock */ + mutex_unlock(&stream->stream_lock); + + return ret; +} +EXPORT_SYMBOL_GPL(snd_sdw_config_ports); + +/** * snd_sdw_master_stop_clock: Stop the clock. This function broadcasts the * SCP_CTRL register with clock_stop_now bit set. * diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h index bbba27a..fc738b8 100644 --- a/sound/sdw/sdw_priv.h +++ b/sound/sdw/sdw_priv.h @@ -68,6 +68,31 @@ #define SDW_NUM_OF_MSG3_XFRD 3 #define SDW_NUM_OF_MSG4_XFRD 4
+/** + * Below values are not defined in MIPI standard. Completely arbitrary + * values that can be changed at will. + */ +#define SDW_MAX_STREAM_TAG_KEY_SIZE 80 +#define SDW_NUM_STREAM_TAGS 100 /* Max number of stream tags */ +#define SDW_DOUBLE_RATE_FACTOR 2 /* Double rate */ + +/* TODO: Description to be provided for SDW_FREQ_MOD_FACTOR */ +#define SDW_FREQ_MOD_FACTOR 3000 + +/** + * SDW_STRM_RATE_GROUPING is place holder number used to hold the frame rate + * used in grouping stream for efficiently calculating bandwidth. All the + * streams with same frame rates belong to same group. This number is + * dynamically increased if the group count number increases above 12. + */ +#define SDW_STRM_RATE_GROUPING 12 + +/* Size of buffer in bytes. */ +#define SDW_BUF_SIZE1 1 +#define SDW_BUF_SIZE2 2 +#define SDW_BUF_SIZE3 3 +#define SDW_BUF_SIZE4 4 + /* Maximum number of Data Ports. */ #define SDW_MAX_DATA_PORTS 15
@@ -80,6 +105,8 @@ */ #define SDW_INTR_STAT_READ_MAX_TRIES 10
+extern struct snd_sdw_core snd_sdw_core; + /** * sdw_driver: Structure to typecast both Master and Slave driver to generic * SoundWire driver, to find out the driver type. @@ -95,6 +122,278 @@ struct sdw_driver { container_of(d, struct sdw_driver, driver)
/** + * sdw_stream_state: Stream state maintained by bus driver for performing + * stream operations. + * + * @SDW_STATE_STRM_ALLOC: New stream is allocated. + * @SDW_STATE_STRM_CONFIG: Stream is configured. PCM/PDM parameters of the + * Stream is updated to bus driver. + * + * @SDW_STATE_STRM_PREPARE: Stream is Prepared. All the ports of Master and + * Slave associated with this stream is prepared for enabling. + * + * @SDW_STATE_STRM_ENABLE: Stream is enabled. All the ports of Master and + * Slave associated with this stream are enable and now stream is + * active. + * + * @SDW_STATE_STRM_DISABLE: Stream in disabled state, All the ports of + * Master and Slave associated with the stream are disabled, and stream + * is not active on bus. + * + * @SDW_STATE_STRM_DEPREPARE: Stream in de-prepare state. All the ports of + * Master and Slave associated with the stream are de-prepared. + * + * @SDW_STATE_STRM_RELEASE: Stream in release state. Stream is not having + * any PCM/PDM configuration. There is not Free state for stream, since + * memory for the stream gets freed, and there is no way to update + * stream as free. + */ +enum sdw_stream_state { + SDW_STATE_STRM_ALLOC = 0, + SDW_STATE_STRM_CONFIG = 1, + SDW_STATE_STRM_PREPARE = 2, + SDW_STATE_STRM_ENABLE = 3, + SDW_STATE_STRM_DISABLE = 4, + SDW_STATE_STRM_DEPREPARE = 5, + SDW_STATE_STRM_RELEASE = 6, +}; + +/** + * sdw_update_bus_ops: Operations performed by bus driver for stream state + * transitions. Some of the operations are performed on individual + * streams, while some are global operations affecting all the streams + * on the bus. + * + * @SDW_BUS_PORT_PRE: Perform all the operations which is to be done before + * initiating the bank switch for stream getting enabled. Master and + * Slave driver may need to perform some operations before bank switch. + * Call Master and Slave handlers to accomplish device specific + * operations before initiating bank switch. + * + * @SDW_BUS_BANK_SWITCH: Initiate the bank switch operation by broadcasting + * SCP_FrameCtrl register. Depending upon the Master implementation + * broadcast will be finished as a part of this state, or Master may + * set some register as a part of PORT_POST below operation after which + * broadcast will be finished. Initiation of the broadcast message is + * done as part of this operation. Broadcast message gets transmitted + * on the bus during this or next operation is Master dependent. + * + * @SDW_BUS_PORT_POST: Perform all the operations which are do be done after + * initiating the Bank switch. Call Master and Slave handlers to + * perform Post bank switch operation. + * + * @SDW_BUS_BANK_SWITCH_WAIT: Bus driver waits here for the Bank switch to + * be completed. This is used for Master(s) running in aggregation mode + * where pre and post operations are performed before and after Bank + * switch operation. The Bank switch message broadcast will happen only + * when clock is enabled which is done as part of post Bank switch + * operation. After post Bank switch operation, bus driver waits for + * response of Bank switch. The bus driver provides SDW_BUS_PORT_PRE + * and SDW_BUS_PORT_POST for Bank switch operation which are as per + * Master implementation. + * + * @SDW_BUS_PORT_DIS_CHN: Disable all the ports of the alternate bank + * (unused bank) after the bank switch. Once Bank switch operation is + * successful, the running stream(s) enabled port channels on previous + * bank needs to be disabled for both Master(s) and Slave(s). + */ +enum sdw_update_bus_ops { + SDW_BUS_PORT_PRE, + SDW_BUS_BANK_SWITCH, + SDW_BUS_PORT_POST, + SDW_BUS_BANK_SWITCH_WAIT, + SDW_BUS_PORT_DIS_CHN, +}; + +/** + * sdw_stream_tag: Stream tag represents the unique SoundWire Audio stream. + * All the ports of the Master(s) and Slave(s) part of the same stream + * tags gets enabled/disabled as a part of single bank Switch.If + * samples of the stream are split between the Master(s), its Master + * responsibility of synchronizing the bank switch of two individual + * Masters. + * + * @stream_tag: Unique stream tag number. + * @stream_lock: Lock for stream. + * @ref_count: Number of times stream tag is allocated. Stream tag is is + * available for allocation if reference count is 0. + * + * @sdw_rt: Holds the stream runtime information. + */ +struct sdw_stream_tag { + int stream_tag; + struct mutex stream_lock; + int ref_count; + struct sdw_runtime *sdw_rt; +}; + +/** + * sdw_stream_params: Stream parameters. + * + * @rate: Sampling frequency + * @channel_count: Number of channels. + * @bps: bits per sample. + */ +struct sdw_stream_params { + unsigned int rate; + unsigned int channel_count; + unsigned int bps; +}; + +/** + * sdw_port_runtime: Holds the port parameters for each of the Master(s) + * Slave(s) port associated with the stream. + * + * @port_num: Port number. + * @channel_mask: Channels of the Stream handled by this port. + * @transport_params: Transport parameters of port. + * @port_params: Port parameters + * @port_node: Port runtime is added to the Port List of Master(s) or + * Slave(s) associated with stream. Node to add the Port runtime to + * Master(s) or Slave(s) list. + */ +struct sdw_port_runtime { + int port_num; + int channel_mask; + struct sdw_transport_params transport_params; + struct sdw_port_params port_params; + struct list_head port_node; +}; + +/** + * sdw_slave_runtime: Holds the Stream parameters for the Slave associated + * with the stream. + * + * @slv: Slave handle associated with this Stream. + * @sdw_rt: Stream handle to which this Slave stream is associated. + * @direction: Port Direction of the Slave for this Stream. Slave is + * transmitting the Data or receiving the Data. + * + * @stream_params: Stream parameters for Slave. + * @port_rt_list: List of Slave Ports associated with this Stream. + * @slave_strm_node: Stream runtime data structure maintains list of all the + * Slave runtime instances associated with stream. This is the node to + * add Slave runtime instance to that list. This list is used for the + * stream configuration. + * + * @slave_mstr_node: Master runtime data structure maintains list of all the + * Slave runtime instances. This is the node to add Slave runtime + * instance to that list. This list is used for Bandwidth calculation + * per bus. Slave runtime instance gets added to two list one for + * stream configuration and other for bandwidth calculation. Stream + * configuration is per stream where there may be multiple Masters and + * Slave associated with Stream. Bandwidth calculation is per Bus, + * where there is Single Master and Multiple Slaves associated with + * bus. + */ +struct sdw_slv_runtime { + struct sdw_slave *slv; + struct sdw_runtime *sdw_rt; + int direction; + struct sdw_stream_params stream_params; + struct list_head port_rt_list; + struct list_head slave_strm_node; + struct list_head slave_mstr_node; +}; + +/** + * sdw_bus_runtime: This structure holds the transport params and BW + * required by the stream on the bus. There may be multiple bus + * associated with the stream. This holds bus specific parameters of + * stream. TODO: Currently sdw_bus_runtime is part of sdw_mstr_runtime + * Master handle. Once stream between Slave to Slave is supported by + * bus driver, this needs to be made part of sdw_runtime handle. + * + * @stream_bw: Bus Bandwidth required by this stream (bps). + * @hstart: Horizontal Start column for this stream. + * @hstop: Horizontal stop column for this stream. + * @block_offset: Block offset for this stream. + * @sub_block_offset: Sub Block offset for this stream. + */ +struct sdw_bus_runtime { + unsigned int stream_bw; + int hstart; + int hstop; + int block_offset; + int sub_block_offset; + +}; + +/** + * sdw_mstr_runtime: Holds the Stream parameters for the Master associated + * with the stream. + * + * @mstr: Master handle associated with this stream. + * @sdw_rt: Stream handle to which this Master stream is associated. + * @stream_params: Stream parameters. + * @port_rt_list: List of this Master Ports associated with this Stream. + * @mstr_strm_node: Stream runtime data structure maintains list of all the + * Master runtime instances associated with stream. This is the node to + * add Master runtime instance to that list. This list is used for the + * stream configuration. + * + * @mstr_node: Master data structure maintains list of all the Master + * runtime instances. This is the node to add Master runtime instance + * to to that list. This list is used for Bandwidth calculation per + * bus. Master runtime instance gets added to two list one for stream + * configuration and other for bandwidth calculation. Stream + * configuration is per stream where there may be multiple Masters and + * Slave associated with Stream. Bandwidth calculation is per Bus, + * where there is Single Master and Multiple Slaves associated with + * bus. + * + * @slv_rt_list: List of the Slave_runtime instances associated with this + * Master_runtime. Its list of all the Slave(s) stream associated with + * this Master. There may be stereo stream from Master to two Slaves, + * where L and R samples from Master is received by two different + * Slave(s), so this list contains the runtime structure associated + * with both Slaves. + * + * @bus_rt: Bus parameters for the stream. There may be multiple bus + * associated with stream. This bus_rt is for current Master. + */ +struct sdw_mstr_runtime { + struct sdw_master *mstr; + struct sdw_runtime *sdw_rt; + int direction; + struct sdw_stream_params stream_params; + struct list_head port_rt_list; + struct list_head mstr_strm_node; + struct list_head mstr_node; + struct list_head slv_rt_list; + struct sdw_bus_runtime bus_rt; +}; + +/** + * sdw_runtime: This structure holds runtime information for each unique + * SoundWire stream. + * + * @tx_ref_count: Number of Transmit devices of stream. This may include + * multiple Master(s) and Slave(s) based on how stream samples are + * split between Mater and Slaves. + * @rx_ref_count: Number of Receive devices of stream. This may include + * multiple Master(s) and Slave(s) based on how stream samples are + * split between Mater and Slaves. + * + * @stream_params: Steam parameters. + * @slv_rt_list: List of the slaves part of this stream. + * @mstr_rt_list: List of Masters part of this stream. + * @type: Stream type PCM or PDM.This is not SoundWire concept, its used + * inside bus driver for efficient BW management. + * + * @stream_state: Current State of the stream. + */ +struct sdw_runtime { + int tx_ref_count; + int rx_ref_count; + struct sdw_stream_params stream_params; + struct list_head slv_rt_list; + struct list_head mstr_rt_list; + enum sdw_stream_type type; + enum sdw_stream_state stream_state; +}; + +/** * sdw_slv_status: List of Slave status. * * @node: Node for adding status to list of Slave status. @@ -110,6 +409,20 @@ struct sdw_slv_status { * * @bus_node: Node to add the bus in the sdw_core list. * @mstr: Master reference for the bus. + * @clk_state: State of the clock. + * @active_bank: Current bank in use. + * @max_clk_dr_freq: Maximum double rate clock frequency. This is maximum + * double clock rate supported per bus. + * @curr_clk_dr_freq: Current double rate clock frequency in use. This is + * current clock rate at which bus is running. + * + * @clk_div: Current clock divider in use. + * @bandwidth: Total bandwidth. + * @system_interval: Bus System interval (Stream Synchronization Point). + * @stream_interval: Stream interval. + * @frame_freq: SoundWire Frame frequency on bus. + * @col: Active columns. + * @row: Active rows. * @status_thread: Thread to process the Slave status. * @kworker: Worker for updating the Slave status. * @kwork: Work for worker @@ -121,16 +434,48 @@ struct sdw_slv_status { * context, spinlock is used to put the status reported by Master into * the status list which is processed by bus driver in thread context * later. + * + * @data: Data to be provided by bus driver for calling xfer_msg_deferred + * callback of Master driver. + */
struct sdw_bus { struct list_head bus_node; struct sdw_master *mstr; + unsigned int clk_state; + unsigned int active_bank; + unsigned int max_dr_clk_freq; + unsigned int curr_dr_clk_freq; + unsigned int clk_div; + unsigned int bandwidth; + unsigned int system_interval; + unsigned int stream_interval; + unsigned int frame_freq; + unsigned int col; + unsigned int row; struct task_struct *status_thread; struct kthread_worker kworker; struct kthread_work kwork; struct list_head status_list; spinlock_t spinlock; + struct sdw_deferred_xfer_data data; +}; + +/** + * sdw_row_col_pair: Information for each row column pair. This is used by + * bus driver for quick BW calculation. + * + * @row: Number of rows. + * @col: Number of columns + * @control_bits: Number of controls bits for this row-column pair. + * @data_bits: Number of controls bits for this row-column pair. + */ +struct sdw_row_col_pair { + int row; + int col; + int control_bits; + int data_bits; };
/** @@ -138,11 +483,17 @@ struct sdw_bus { * spawned across masters and has list of bus structure per every * Master registered. * + * @row_col_pair: Array holding all row-column pair possible as per MIPI + * 1.1 Spec. This is used for quick reference for BW calculation + * algorithm. + * * @bus_list: List of all the bus instance. * @core_mutex: Global lock for all bus instances. * @idr: For identifying the registered buses. */ struct snd_sdw_core { + struct sdw_stream_tag stream_tags[SDW_NUM_STREAM_TAGS]; + struct sdw_row_col_pair row_col_pair[MAX_NUM_ROW_COLS]; struct list_head bus_list; struct mutex core_mutex; struct idr idr; @@ -168,6 +519,58 @@ void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg, struct sdw_deferred_xfer_data *data);
/** + * sdw_index_to_col: Structure holding mapping of numbers to columns. + * + * @index: Holds index to number of columns. + * @col: Holds actual columns. + */ +struct sdw_index_to_col { + int index; + int col; +}; + +/** + * sdw_index_to_row: Structure holding mapping of numbers to rows. + * + * @index: Holds index to number of rows. + * @row: Holds actual rows. + */ +struct sdw_index_to_row { + int index; + int row; +}; + +/** + * sdw_group_params: Structure holding temporary variable while computing + * transport parameters of Master(s) and Slave(s). + * + * @rate: Holds stream rate. + * @full_bw: Holds full bandwidth per group. + * @payload_bw: Holds payload bandwidth per group. + * @hwidth: Holds hwidth per group. + */ +struct sdw_group_params { + int rate; + int full_bw; + int payload_bw; + int hwidth; +}; + +/** + * sdw_group_count: Structure holding group count and stream rate array + * while computing transport parameters of Master(s) and Slave(s). + * + * @group_count: Holds actual group count. + * @max_size: Holds maximum capacity of array. + * @stream_rates: Pointer to stream rates. + */ +struct sdw_group_count { + unsigned int group_count; + unsigned int max_size; + unsigned int *stream_rates; +}; + +/** * sdw_enable_disable_dpn_intr: Enable or Disable Slave Data Port interrupt. * This is called by bus driver before prepare and after deprepare of * the ports. @@ -182,6 +585,52 @@ void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg, int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num, int port_direction, bool enable);
+/** + * sdw_create_row_col_pair: Initialization of bandwidth related operations. + * This is required to have fast path for the BW calculation when a new + * stream is prepared or deprepared. This is called only once as part + * of SoundWire Bus driver getting initialized. + */ +void sdw_create_row_col_pair(void); + +/** + * sdw_init_bus_params: Sets up bus data structure for BW calculation. This + * is called once per each Master interface registration to the + * SoundWire bus. + * + * @sdw_bus: Bus handle. + */ +void sdw_init_bus_params(struct sdw_bus *sdw_bus); + +/** + * sdw_get_slv_dpn_caps: Get the data port capabilities based on the port + * number and port direction. + * + * @slv_cap: Slave capabilities. + * @direction: Port data direction. + * @port_num: Port number. + */ +struct sdw_dpn_caps *sdw_get_slv_dpn_cap(struct sdw_slave_caps *slv_cap, + enum sdw_data_direction direction, + unsigned int port_num); + +/* Return bus structure */ +static inline struct sdw_bus *sdw_master_to_bus(struct sdw_master *mstr) +{ + return mstr->bus; +} + +/* Reference count increment */ +static inline void sdw_inc_ref_count(int *ref_count) +{ + (*ref_count)++; +} + +/* Reference count decrement */ +static inline void sdw_dec_ref_count(int *ref_count) +{ + (*ref_count)--; +}
/* * Helper function for bus driver to write messages. Since bus driver
On Fri, Oct 21, 2016 at 06:10:58PM +0530, Hardik Shah wrote:
Following RFC series adds SoundWire bus driver interface based on the MIPI SoundWire specification 1.1
This is adding a new bus so you should get Greg to review. It also seems implausible that SoundWire is going to be exclusively used by audio devices so you should add the bus level code in drivers/ rather than sound/, that will make life easier when other users appear.
On Mon, Nov 14, 2016 at 12:11:23PM +0000, Mark Brown wrote:
On Fri, Oct 21, 2016 at 06:10:58PM +0530, Hardik Shah wrote:
Following RFC series adds SoundWire bus driver interface based on the MIPI SoundWire specification 1.1
This is adding a new bus so you should get Greg to review. It also seems implausible that SoundWire is going to be exclusively used by audio devices so you should add the bus level code in drivers/ rather than sound/, that will make life easier when other users appear.
Yeah he was supposed to be on the CC list, but somehow got missed in the end. I did ping him though and he wants to review only when it is ready to merge and not yet..
participants (5)
-
Charles Keepax
-
Hardik Shah
-
Mark Brown
-
Pierre-Louis Bossart
-
Vinod Koul