[alsa-devel] [PATCH v4 00/15] soundwire: Add a new SoundWire subsystem
This patch series adds a new SoundWire subsystem which implements a new MIPI bus protocol 'SoundWire'.
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.
This series adds SoundWire Bus, IO transfers, DisCo (Discovery and Configuration) sysfs interface, regmap and Documentation summary
This patch series is also available on git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git topic/patch_v4
v3: https://lkml.org/lkml/2017/11/30/160 v2: https://lkml.org/lkml/2017/11/10/216 v1: https://lkml.org/lkml/2017/10/18/1030 RFC: https://lkml.org/lkml/2016/10/21/395
Changes in v4: - Remove text licenses and add SPDX tags only with C99 style comments - make bus_type code as GPL v2.0 only
Changes in v3: - Update the kernel-doc styles and fix included headers for files - handle dev_pm_domain_attach() for defered probe - remove OF placeholders - change regmap license to GPLv2 only
Changes in v2: - move documentation into driver-api and do rst conversion - fix documentation comments - add SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) to all source files - rework the transfer logic and paging logic as commented on v1 - remove dummy sysfs fns - registration checks and fixes - remove slave check for regamp as that turned superfluous - remove depends SoundWire symbol - make modalias api arg const - use bitmap for tracking assigned - add counter for report present tracking todo: add the dt-bindings
Sanyog Kale (4): Documentation: Add SoundWire summary soundwire: Add SoundWire MIPI defined registers soundwire: Add Slave status handling helpers soundwire: cdns: Add sdw_master_ops and IO transfer support
Vinod Koul (11): soundwire: Add SoundWire bus type soundwire: Add Master registration soundwire: Add MIPI DisCo property helpers soundwire: Add IO transfer regmap: Add SoundWire bus support soundwire: Add slave status handling soundwire: Add sysfs for SoundWire DisCo properties soundwire: cdns: Add cadence library soundwire: intel: Add Intel Master driver soundwire: intel: Add Intel init module MAINTAINERS: Add SoundWire entry
Documentation/driver-api/index.rst | 1 + Documentation/driver-api/soundwire/index.rst | 15 + Documentation/driver-api/soundwire/summary.rst | 205 ++++++ MAINTAINERS | 10 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/base/regmap/Kconfig | 4 + drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-sdw.c | 92 +++ drivers/soundwire/Kconfig | 37 + drivers/soundwire/Makefile | 18 + drivers/soundwire/bus.c | 958 +++++++++++++++++++++++++ drivers/soundwire/bus.h | 74 ++ drivers/soundwire/bus_type.c | 193 +++++ drivers/soundwire/cadence_master.c | 749 +++++++++++++++++++ drivers/soundwire/cadence_master.h | 48 ++ drivers/soundwire/intel.c | 347 +++++++++ drivers/soundwire/intel.h | 23 + drivers/soundwire/intel_init.c | 198 +++++ drivers/soundwire/mipi_disco.c | 374 ++++++++++ drivers/soundwire/slave.c | 115 +++ drivers/soundwire/sysfs.c | 343 +++++++++ include/linux/mod_devicetable.h | 6 + include/linux/regmap.h | 37 + include/linux/soundwire/sdw.h | 487 +++++++++++++ include/linux/soundwire/sdw_intel.h | 24 + include/linux/soundwire/sdw_registers.h | 194 +++++ include/linux/soundwire/sdw_type.h | 19 + scripts/mod/devicetable-offsets.c | 4 + scripts/mod/file2alias.c | 15 + 30 files changed, 4594 insertions(+) create mode 100644 Documentation/driver-api/soundwire/index.rst create mode 100644 Documentation/driver-api/soundwire/summary.rst create mode 100644 drivers/base/regmap/regmap-sdw.c create mode 100644 drivers/soundwire/Kconfig create mode 100644 drivers/soundwire/Makefile create mode 100644 drivers/soundwire/bus.c create mode 100644 drivers/soundwire/bus.h create mode 100644 drivers/soundwire/bus_type.c create mode 100644 drivers/soundwire/cadence_master.c create mode 100644 drivers/soundwire/cadence_master.h create mode 100644 drivers/soundwire/intel.c create mode 100644 drivers/soundwire/intel.h create mode 100644 drivers/soundwire/intel_init.c create mode 100644 drivers/soundwire/mipi_disco.c create mode 100644 drivers/soundwire/slave.c create mode 100644 drivers/soundwire/sysfs.c create mode 100644 include/linux/soundwire/sdw.h create mode 100644 include/linux/soundwire/sdw_intel.h create mode 100644 include/linux/soundwire/sdw_registers.h create mode 100644 include/linux/soundwire/sdw_type.h
From: Sanyog Kale sanyog.r.kale@intel.com
SoundWire is a new Linux bus which implements a new MIPI bus protocol 'SoundWire'. The summary of SoundWire bus and API is documented in the 'summary' file.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- Documentation/driver-api/index.rst | 1 + Documentation/driver-api/soundwire/index.rst | 15 ++ Documentation/driver-api/soundwire/summary.rst | 205 +++++++++++++++++++++++++ 3 files changed, 221 insertions(+) create mode 100644 Documentation/driver-api/soundwire/index.rst create mode 100644 Documentation/driver-api/soundwire/summary.rst
diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst index d17a9876b473..17992b9e4a55 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -47,6 +47,7 @@ available subsections can be seen below. gpio misc_devices dmaengine/index + soundwire/index
.. only:: subproject and html
diff --git a/Documentation/driver-api/soundwire/index.rst b/Documentation/driver-api/soundwire/index.rst new file mode 100644 index 000000000000..647e94654752 --- /dev/null +++ b/Documentation/driver-api/soundwire/index.rst @@ -0,0 +1,15 @@ +======================= +SoundWire Documentation +======================= + +.. toctree:: + :maxdepth: 1 + + summary + +.. only:: subproject + + Indices + ======= + + * :ref:`genindex` diff --git a/Documentation/driver-api/soundwire/summary.rst b/Documentation/driver-api/soundwire/summary.rst new file mode 100644 index 000000000000..51b82c1972f5 --- /dev/null +++ b/Documentation/driver-api/soundwire/summary.rst @@ -0,0 +1,205 @@ +=========================== +SoundWire Subsystem Summary +=========================== + +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 multi-drop 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 an example of connectivity between a SoundWire Master and +two Slave devices. :: + + +---------------+ +---------------+ + | | Clock Signal | | + | Master |-------+-------------------------------| Slave | + | Interface | | Data Signal | Interface 1 | + | |-------|-------+-----------------------| | + +---------------+ | | +---------------+ + | | + | | + | | + +--+-------+--+ + | | + | Slave | + | Interface 2 | + | | + +-------------+ + + +Terminology +=========== + +The MIPI SoundWire specification uses the term 'device' to refer to a Master +or Slave interface, which of course can be confusing. In this summary and +code we use the term interface only to refer to the hardware. We follow the +Linux device model by mapping each Slave interface connected on the bus as a +device managed by a specific driver. The Linux SoundWire subsystem provides +a framework to implement a SoundWire Slave driver with an API allowing +3rd-party vendors to enable implementation-defined functionality while +common setup/configuration tasks are handled by the bus. + +Bus: +Implements SoundWire Linux Bus which handles the SoundWire protocol. +Programs all the MIPI-defined Slave registers. Represents a SoundWire +Master. Multiple instances of Bus may be present in a system. + +Slave: +Registers as SoundWire Slave device (Linux Device). Multiple Slave devices +can register to a Bus instance. + +Slave driver: +Driver controlling the Slave device. MIPI-specified registers are controlled +directly by the Bus (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 supports programming interfaces for the SoundWire Master +implementation 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 interfaces needs to be registered to the Bus. +Bus implements API to read standard Master MIPI properties and also provides +callback in Master ops for Master driver to implement its own functions that +provides capabilities information. DT support is not implemented at this +time but should be trivial to add since capabilities are enabled with the +``device_property_`` API. + +The Master interface along with the Master interface capabilities are +registered based on board file, DT or ACPI. + +Following is the Bus API to register the SoundWire Bus: + +.. code-block:: c + + int sdw_add_bus_master(struct sdw_bus *bus) + { + if (!bus->dev) + return -ENODEV; + + mutex_init(&bus->lock); + INIT_LIST_HEAD(&bus->slaves); + + /* Check ACPI for Slave devices */ + sdw_acpi_find_slaves(bus); + + /* Check DT for Slave devices */ + sdw_of_find_slaves(bus); + + return 0; + } + +This will initialize sdw_bus object for Master device. "sdw_master_ops" and +"sdw_master_port_ops" callback functions are provided to the Bus. + +"sdw_master_ops" is used by Bus to control the Bus in the hardware specific +way. It includes Bus control functions such as sending the SoundWire +read/write messages on Bus, setting up clock frequency & Stream +Synchronization Point (SSP). The "sdw_master_ops" structure abstracts the +hardware details of the Master from the Bus. + +"sdw_master_port_ops" is used by Bus to setup the Port parameters of the +Master interface Port. Master interface Port register map is not defined by +MIPI specification, so Bus calls the "sdw_master_port_ops" callback +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 a specific vendor and part +identifier, Bus enumerates the Slave device based on these two ids. +Slave device and driver match is done based on these two ids . Probe +of the Slave driver is called by Bus on successful match between device and +driver id. A parent/child relationship is enforced between Master and Slave +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 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. + +.. code-block:: c + + static const struct sdw_device_id slave_id[] = { + SDW_SLAVE_ENTRY(0x025d, 0x700, 0), + {}, + }; + MODULE_DEVICE_TABLE(sdw, slave_id); + + static struct sdw_driver slave_sdw_driver = { + .driver = { + .name = "slave_xxx", + .pm = &slave_runtime_pm, + }, + .probe = slave_sdw_probe, + .remove = slave_sdw_remove, + .ops = &slave_slave_ops, + .id_table = slave_id, + }; + + +For capabilities, Bus implements API to read standard Slave MIPI properties +and also provides callback in Slave ops for Slave driver to implement own +function that provides capabilities information. Bus needs to know a set of +Slave capabilities to program Slave registers and to control the Bus +reconfigurations. + +Future enhancements to be done +============================== + + (1) Bulk Register Access (BRA) transfers. + + + (2) Multiple data lane support. + +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 +available at: +https://www.mipi.org/specifications/mipi-disco-soundwire + +(publicly accessible with registration or directly accessible to MIPI +members)
This adds the base SoundWire bus type, bus and driver registration. along with changes to module device table for new SoundWire device type.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/soundwire/Kconfig | 22 +++++ drivers/soundwire/Makefile | 7 ++ drivers/soundwire/bus_type.c | 172 +++++++++++++++++++++++++++++++++++++ include/linux/mod_devicetable.h | 6 ++ include/linux/soundwire/sdw.h | 111 ++++++++++++++++++++++++ include/linux/soundwire/sdw_type.h | 19 ++++ scripts/mod/devicetable-offsets.c | 4 + scripts/mod/file2alias.c | 15 ++++ 10 files changed, 359 insertions(+) create mode 100644 drivers/soundwire/Kconfig create mode 100644 drivers/soundwire/Makefile create mode 100644 drivers/soundwire/bus_type.c create mode 100644 include/linux/soundwire/sdw.h create mode 100644 include/linux/soundwire/sdw_type.h
diff --git a/drivers/Kconfig b/drivers/Kconfig index 152744c5ef0f..3a0bbbd42292 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -153,6 +153,8 @@ source "drivers/remoteproc/Kconfig"
source "drivers/rpmsg/Kconfig"
+source "drivers/soundwire/Kconfig" + source "drivers/soc/Kconfig"
source "drivers/devfreq/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 1d034b680431..cd13b2ed7ed6 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -156,6 +156,7 @@ obj-$(CONFIG_MAILBOX) += mailbox/ obj-$(CONFIG_HWSPINLOCK) += hwspinlock/ obj-$(CONFIG_REMOTEPROC) += remoteproc/ obj-$(CONFIG_RPMSG) += rpmsg/ +obj-$(CONFIG_SOUNDWIRE) += soundwire/
# Virtualization drivers obj-$(CONFIG_VIRT_DRIVERS) += virt/ diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig new file mode 100644 index 000000000000..d7d3908f4913 --- /dev/null +++ b/drivers/soundwire/Kconfig @@ -0,0 +1,22 @@ +# +# SoundWire subsystem configuration +# + +menuconfig SOUNDWIRE + bool "SoundWire support" + ---help--- + SoundWire is a 2-Pin interface with data and clock line ratified + 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. Say Y to enable this subsystem, N if you do not have such + a device + +if SOUNDWIRE + +comment "SoundWire Devices" + +config SOUNDWIRE_BUS + tristate + +endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile new file mode 100644 index 000000000000..d1281def7662 --- /dev/null +++ b/drivers/soundwire/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for soundwire core +# + +#Bus Objs +soundwire-bus-objs := bus_type.o +obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c new file mode 100644 index 000000000000..8d8dcc68e9a8 --- /dev/null +++ b/drivers/soundwire/bus_type.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2015-17 Intel Corporation. + +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/pm_domain.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> + +/** + * sdw_get_device_id - find the matching SoundWire device id + * @slave: SoundWire Slave Device + * @drv: SoundWire Slave Driver + * + * The match is done by comparing the mfg_id and part_id from the + * struct sdw_device_id. + */ +static const struct sdw_device_id * +sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv) +{ + const struct sdw_device_id *id = drv->id_table; + + while (id && id->mfg_id) { + if (slave->id.mfg_id == id->mfg_id && + slave->id.part_id == id->part_id) + return id; + id++; + } + + return NULL; +} + +static int sdw_bus_match(struct device *dev, struct device_driver *ddrv) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(ddrv); + + return !!sdw_get_device_id(slave, drv); +} + +int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size) +{ + /* modalias is sdw:m<mfg_id>p<part_id> */ + + return snprintf(buf, size, "sdw:m%04Xp%04X\n", + slave->id.mfg_id, slave->id.part_id); +} + +static int sdw_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + char modalias[32]; + + sdw_slave_modalias(slave, modalias, sizeof(modalias)); + + if (add_uevent_var(env, "MODALIAS=%s", modalias)) + return -ENOMEM; + + return 0; +} + +struct bus_type sdw_bus_type = { + .name = "soundwire", + .match = sdw_bus_match, + .uevent = sdw_uevent, +}; +EXPORT_SYMBOL_GPL(sdw_bus_type); + +static int sdw_drv_probe(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + const struct sdw_device_id *id; + int ret; + + id = sdw_get_device_id(slave, drv); + if (!id) + return -ENODEV; + + /* + * attach to power domain but don't turn on (last arg) + */ + ret = dev_pm_domain_attach(dev, false); + if (ret != -EPROBE_DEFER) { + ret = drv->probe(slave, id); + if (ret) { + dev_err(dev, "Probe of %s failed: %d\n", drv->name, ret); + dev_pm_domain_detach(dev, false); + } + } + + return ret; +} + +static int sdw_drv_remove(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + int ret = 0; + + if (drv->remove) + ret = drv->remove(slave); + + dev_pm_domain_detach(dev, false); + + return ret; +} + +static void sdw_drv_shutdown(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + + if (drv->shutdown) + drv->shutdown(slave); +} + +/** + * __sdw_register_driver() - register a SoundWire Slave driver + * @drv: driver to register + * @owner: owning module/driver + * + * Return: zero on success, else a negative error code. + */ +int __sdw_register_driver(struct sdw_driver *drv, struct module *owner) +{ + drv->driver.bus = &sdw_bus_type; + + if (!drv->probe) { + pr_err("driver %s didn't provide SDW probe routine\n", + drv->name); + return -EINVAL; + } + + drv->driver.owner = owner; + drv->driver.probe = sdw_drv_probe; + + if (drv->remove) + drv->driver.remove = sdw_drv_remove; + + if (drv->shutdown) + drv->driver.shutdown = sdw_drv_shutdown; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__sdw_register_driver); + +/** + * sdw_unregister_driver() - unregisters the SoundWire Slave driver + * @drv: driver to unregister + */ +void sdw_unregister_driver(struct sdw_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(sdw_unregister_driver); + +static int __init sdw_bus_init(void) +{ + return bus_register(&sdw_bus_type); +} + +static void __exit sdw_bus_exit(void) +{ + bus_unregister(&sdw_bus_type); +} + +postcore_initcall(sdw_bus_init); +module_exit(sdw_bus_exit); + +MODULE_DESCRIPTION("SoundWire bus"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h index abb6dc2ebbf8..d6f727a68eb1 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h @@ -229,6 +229,12 @@ struct hda_device_id { unsigned long driver_data; };
+struct sdw_device_id { + __u16 mfg_id; + __u16 part_id; + kernel_ulong_t driver_data; +}; + /* * Struct used for matching a device */ diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h new file mode 100644 index 000000000000..4a7503781602 --- /dev/null +++ b/include/linux/soundwire/sdw.h @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SOUNDWIRE_H +#define __SOUNDWIRE_H + +struct sdw_bus; +struct sdw_slave; + +#define SDW_MAX_DEVICES 11 + +/** + * enum sdw_slave_status - Slave status + * @SDW_SLAVE_UNATTACHED: Slave is not attached with the bus. + * @SDW_SLAVE_ATTACHED: Slave is attached with bus. + * @SDW_SLAVE_ALERT: Some alert condition on the Slave + * @SDW_SLAVE_RESERVED: Reserved for future use + */ +enum sdw_slave_status { + SDW_SLAVE_UNATTACHED = 0, + SDW_SLAVE_ATTACHED = 1, + SDW_SLAVE_ALERT = 2, + SDW_SLAVE_RESERVED = 3, +}; + +/* + * SDW Slave Structures and APIs + */ + +/** + * struct sdw_slave_id - Slave ID + * @mfg_id: MIPI Manufacturer ID + * @part_id: Device Part ID + * @class_id: MIPI Class ID, unused now. + * Currently a placeholder in MIPI SoundWire Spec + * @unique_id: Device unique ID + * @sdw_version: SDW version implemented + * + * The order of the IDs here does not follow the DisCo spec definitions + */ +struct sdw_slave_id { + __u16 mfg_id; + __u16 part_id; + __u8 class_id; + __u8 unique_id:4; + __u8 sdw_version:4; +}; + +/** + * struct sdw_slave - SoundWire Slave + * @id: MIPI device ID + * @dev: Linux device + * @status: Status reported by the Slave + * @bus: Bus handle + * @node: node for bus list + * @dev_num: Device Number assigned by Bus + */ +struct sdw_slave { + struct sdw_slave_id id; + struct device dev; + enum sdw_slave_status status; + struct sdw_bus *bus; + struct list_head node; + u16 dev_num; +}; + +#define dev_to_sdw_dev(_dev) container_of(_dev, struct sdw_slave, dev) + +struct sdw_driver { + const char *name; + + int (*probe)(struct sdw_slave *sdw, + const struct sdw_device_id *id); + int (*remove)(struct sdw_slave *sdw); + void (*shutdown)(struct sdw_slave *sdw); + + const struct sdw_device_id *id_table; + const struct sdw_slave_ops *ops; + + struct device_driver driver; +}; + +#define SDW_SLAVE_ENTRY(_mfg_id, _part_id, _drv_data) \ + { .mfg_id = (_mfg_id), .part_id = (_part_id), \ + .driver_data = (unsigned long)(_drv_data) } + +int sdw_handle_slave_status(struct sdw_bus *bus, + enum sdw_slave_status status[]); + +/* + * SDW master structures and APIs + */ + +/** + * struct sdw_bus - SoundWire bus + * @dev: Master linux device + * @link_id: Link id number, can be 0 to N, unique for each Master + * @slaves: list of Slaves on this bus + * @assigned: Bitmap for Slave device numbers. + * Bit set implies used number, bit clear implies unused number. + * @bus_lock: bus lock + */ +struct sdw_bus { + struct device *dev; + unsigned int link_id; + struct list_head slaves; + DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); + struct mutex bus_lock; +}; + +#endif /* __SOUNDWIRE_H */ diff --git a/include/linux/soundwire/sdw_type.h b/include/linux/soundwire/sdw_type.h new file mode 100644 index 000000000000..9fd553e553e9 --- /dev/null +++ b/include/linux/soundwire/sdw_type.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SOUNDWIRE_TYPES_H +#define __SOUNDWIRE_TYPES_H + +extern struct bus_type sdw_bus_type; + +#define drv_to_sdw_driver(_drv) container_of(_drv, struct sdw_driver, driver) + +#define sdw_register_driver(drv) \ + __sdw_register_driver(drv, THIS_MODULE) + +int __sdw_register_driver(struct sdw_driver *drv, struct module *); +void sdw_unregister_driver(struct sdw_driver *drv); + +int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size); + +#endif /* __SOUNDWIRE_TYPES_H */ diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c index 9826b9a6543c..9fad6afe4c41 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c @@ -203,6 +203,10 @@ int main(void) DEVID_FIELD(hda_device_id, rev_id); DEVID_FIELD(hda_device_id, api_version);
+ DEVID(sdw_device_id); + DEVID_FIELD(sdw_device_id, mfg_id); + DEVID_FIELD(sdw_device_id, part_id); + DEVID(fsl_mc_device_id); DEVID_FIELD(fsl_mc_device_id, vendor); DEVID_FIELD(fsl_mc_device_id, obj_type); diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c index 6ef6e63f96fd..b9beeaa4695b 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c @@ -1289,6 +1289,21 @@ static int do_hda_entry(const char *filename, void *symval, char *alias) } ADD_TO_DEVTABLE("hdaudio", hda_device_id, do_hda_entry);
+/* Looks like: sdw:mNpN */ +static int do_sdw_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD(symval, sdw_device_id, mfg_id); + DEF_FIELD(symval, sdw_device_id, part_id); + + strcpy(alias, "sdw:"); + ADD(alias, "m", mfg_id != 0, mfg_id); + ADD(alias, "p", part_id != 0, part_id); + + add_wildcard(alias); + return 1; +} +ADD_TO_DEVTABLE("sdw", sdw_device_id, do_sdw_entry); + /* Looks like: fsl-mc:vNdN */ static int do_fsl_mc_entry(const char *filename, void *symval, char *alias)
A Master registers with SoundWire bus and scans the firmware provided for device description. In this patch we scan the ACPI namespaces and create the SoundWire Slave devices based on the ACPI description
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 119 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 19 +++++++ drivers/soundwire/slave.c | 114 ++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 11 ++++ 5 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 drivers/soundwire/bus.c create mode 100644 drivers/soundwire/bus.h create mode 100644 drivers/soundwire/slave.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index d1281def7662..c875e434f8b3 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -3,5 +3,5 @@ #
#Bus Objs -soundwire-bus-objs := bus_type.o +soundwire-bus-objs := bus_type.o bus.o slave.o obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c new file mode 100644 index 000000000000..0f89b2f36938 --- /dev/null +++ b/drivers/soundwire/bus.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#include <linux/acpi.h> +#include <linux/mod_devicetable.h> +#include <linux/soundwire/sdw.h> +#include "bus.h" + +/** + * sdw_add_bus_master() - add a bus Master instance + * @bus: bus instance + * + * Initializes the bus instance, read properties and create child + * devices. + */ +int sdw_add_bus_master(struct sdw_bus *bus) +{ + int ret; + + if (!bus->dev) { + pr_err("SoundWire bus has no device"); + return -ENODEV; + } + + mutex_init(&bus->bus_lock); + INIT_LIST_HEAD(&bus->slaves); + + /* + * Device numbers in SoundWire are 0 thru 15 with 0 being + * Enumeration device number and 15 broadcast device number. So + * they are not used for assignment so mask these and other + * higher bits + */ + + /* Set higher order bits */ + *bus->assigned = ~GENMASK(SDW_BROADCAST_DEV_NUM, SDW_ENUM_DEV_NUM); + + /* Set device number and broadcast device number */ + set_bit(SDW_ENUM_DEV_NUM, bus->assigned); + set_bit(SDW_BROADCAST_DEV_NUM, bus->assigned); + + /* + * SDW is an enumerable bus, but devices can be powered off. So, + * they won't be able to report as present. + * + * Create Slave devices based on Slaves described in + * the respective firmware (ACPI/DT) + */ + if (IS_ENABLED(CONFIG_ACPI) && ACPI_HANDLE(bus->dev)) + ret = sdw_acpi_find_slaves(bus); + else + ret = -ENOTSUPP; /* No ACPI/DT so error out */ + + if (ret) { + dev_err(bus->dev, "Finding slaves failed:%d\n", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(sdw_add_bus_master); + +static int sdw_delete_slave(struct device *dev, void *data) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_bus *bus = slave->bus; + + mutex_lock(&bus->bus_lock); + + if (slave->dev_num) /* clear dev_num if assigned */ + clear_bit(slave->dev_num, bus->assigned); + + list_del_init(&slave->node); + mutex_unlock(&bus->bus_lock); + + device_unregister(dev); + return 0; +} + +/** + * sdw_delete_bus_master() - delete the bus master instance + * @bus: bus to be deleted + * + * Remove the instance, delete the child devices. + */ +void sdw_delete_bus_master(struct sdw_bus *bus) +{ + device_for_each_child(bus->dev, NULL, sdw_delete_slave); +} +EXPORT_SYMBOL(sdw_delete_bus_master); + +void sdw_extract_slave_id(struct sdw_bus *bus, + u64 addr, struct sdw_slave_id *id) +{ + dev_dbg(bus->dev, "SDW Slave Addr: %llx", addr); + + /* + * Spec definition + * Register Bit Contents + * DevId_0 [7:4] 47:44 sdw_version + * DevId_0 [3:0] 43:40 unique_id + * DevId_1 39:32 mfg_id [15:8] + * DevId_2 31:24 mfg_id [7:0] + * DevId_3 23:16 part_id [15:8] + * DevId_4 15:08 part_id [7:0] + * DevId_5 07:00 class_id + */ + id->sdw_version = (addr >> 44) & GENMASK(3, 0); + id->unique_id = (addr >> 40) & GENMASK(3, 0); + id->mfg_id = (addr >> 24) & GENMASK(15, 0); + id->part_id = (addr >> 8) & GENMASK(15, 0); + id->class_id = addr & GENMASK(7, 0); + + dev_dbg(bus->dev, + "SDW Slave class_id %x, part_id %x, mfg_id %x, unique_id %x, version %x", + id->class_id, id->part_id, id->mfg_id, + id->unique_id, id->sdw_version); + +} diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h new file mode 100644 index 000000000000..a54921825ce0 --- /dev/null +++ b/drivers/soundwire/bus.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_BUS_H +#define __SDW_BUS_H + +#if IS_ENABLED(CONFIG_ACPI) +int sdw_acpi_find_slaves(struct sdw_bus *bus); +#else +static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) +{ + return -ENOTSUPP; +} +#endif + +void sdw_extract_slave_id(struct sdw_bus *bus, + u64 addr, struct sdw_slave_id *id); + +#endif /* __SDW_BUS_H */ diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c new file mode 100644 index 000000000000..397e100d66e3 --- /dev/null +++ b/drivers/soundwire/slave.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#include <linux/acpi.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include "bus.h" + +static void sdw_slave_release(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + kfree(slave); +} + +static int sdw_slave_add(struct sdw_bus *bus, + struct sdw_slave_id *id, struct fwnode_handle *fwnode) +{ + struct sdw_slave *slave; + int ret; + + slave = kzalloc(sizeof(*slave), GFP_KERNEL); + if (!slave) + return -ENOMEM; + + /* Initialize data structure */ + memcpy(&slave->id, id, sizeof(*id)); + slave->dev.parent = bus->dev; + slave->dev.fwnode = fwnode; + + /* name shall be sdw:link:mfg:part:class:unique */ + dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x", + bus->link_id, id->mfg_id, id->part_id, + id->class_id, id->unique_id); + + slave->dev.release = sdw_slave_release; + slave->dev.bus = &sdw_bus_type; + slave->bus = bus; + slave->status = SDW_SLAVE_UNATTACHED; + slave->dev_num = 0; + + mutex_lock(&bus->bus_lock); + list_add_tail(&slave->node, &bus->slaves); + mutex_unlock(&bus->bus_lock); + + ret = device_register(&slave->dev); + if (ret) { + dev_err(bus->dev, "Failed to add slave: ret %d\n", ret); + + /* + * On err, don't free but drop ref as this will be freed + * when release method is invoked. + */ + mutex_lock(&bus->bus_lock); + list_del(&slave->node); + mutex_unlock(&bus->bus_lock); + put_device(&slave->dev); + } + + return ret; +} + +#if IS_ENABLED(CONFIG_ACPI) +/* + * sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node + * @bus: SDW bus instance + * + * Scans Master ACPI node for SDW child Slave devices and registers it. + */ +int sdw_acpi_find_slaves(struct sdw_bus *bus) +{ + struct acpi_device *adev, *parent; + + parent = ACPI_COMPANION(bus->dev); + if (!parent) { + dev_err(bus->dev, "Can't find parent for acpi bind\n"); + return -ENODEV; + } + + list_for_each_entry(adev, &parent->children, node) { + unsigned long long addr; + struct sdw_slave_id id; + unsigned int link_id; + acpi_status status; + + status = acpi_evaluate_integer(adev->handle, + METHOD_NAME__ADR, NULL, &addr); + + if (ACPI_FAILURE(status)) { + dev_err(bus->dev, "_ADR resolution failed: %x\n", + status); + return status; + } + + /* Extract link id from ADR, it is from 48 to 51 bits */ + link_id = (addr >> 48) & GENMASK(3, 0); + + /* Check for link_id match */ + if (link_id != bus->link_id) + continue; + + sdw_extract_slave_id(bus, addr, &id); + + /* + * don't error check for sdw_slave_add as we want to continue + * adding Slaves + */ + sdw_slave_add(bus, &id, acpi_fwnode_handle(adev)); + } + + return 0; +} + +#endif diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 4a7503781602..7e9579941c66 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -7,6 +7,14 @@ struct sdw_bus; struct sdw_slave;
+/* SDW spec defines and enums, as defined by MIPI 1.1. Spec */ + +/* SDW Broadcast Device Number */ +#define SDW_BROADCAST_DEV_NUM 15 + +/* SDW Enumeration Device Number */ +#define SDW_ENUM_DEV_NUM 0 + #define SDW_MAX_DEVICES 11
/** @@ -108,4 +116,7 @@ struct sdw_bus { struct mutex bus_lock; };
+int sdw_add_bus_master(struct sdw_bus *bus); +void sdw_delete_bus_master(struct sdw_bus *bus); + #endif /* __SOUNDWIRE_H */
On 12/1/17 3:56 AM, Vinod Koul wrote:
A Master registers with SoundWire bus and scans the firmware provided
nitpick: is the 'register' correct? You create a bus instance for each hardware master interface. Or is my brain fried?
for device description. In this patch we scan the ACPI namespaces and create the SoundWire Slave devices based on the ACPI description
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 119 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 19 +++++++ drivers/soundwire/slave.c | 114 ++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 11 ++++ 5 files changed, 264 insertions(+), 1 deletion(-) create mode 100644 drivers/soundwire/bus.c create mode 100644 drivers/soundwire/bus.h create mode 100644 drivers/soundwire/slave.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index d1281def7662..c875e434f8b3 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -3,5 +3,5 @@ #
#Bus Objs -soundwire-bus-objs := bus_type.o +soundwire-bus-objs := bus_type.o bus.o slave.o obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c new file mode 100644 index 000000000000..0f89b2f36938 --- /dev/null +++ b/drivers/soundwire/bus.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation.
+#include <linux/acpi.h> +#include <linux/mod_devicetable.h> +#include <linux/soundwire/sdw.h> +#include "bus.h"
+/**
- sdw_add_bus_master() - add a bus Master instance
- @bus: bus instance
- Initializes the bus instance, read properties and create child
- devices.
- */
+int sdw_add_bus_master(struct sdw_bus *bus) +{
- int ret;
- if (!bus->dev) {
pr_err("SoundWire bus has no device");
return -ENODEV;
- }
- mutex_init(&bus->bus_lock);
- INIT_LIST_HEAD(&bus->slaves);
- /*
* Device numbers in SoundWire are 0 thru 15 with 0 being
* Enumeration device number and 15 broadcast device number. So
* they are not used for assignment so mask these and other
* higher bits
device 14 is used for the master and is not supported by these patches. While allowed, if you use device 12 and 13 (groups) you can't get the status information in a PING command so the recommendation is to avoid them. Those device numbers should never be used really (on top of 0 and 15 as explained above)
*/
- /* Set higher order bits */
- *bus->assigned = ~GENMASK(SDW_BROADCAST_DEV_NUM, SDW_ENUM_DEV_NUM);
- /* Set device number and broadcast device number */
- set_bit(SDW_ENUM_DEV_NUM, bus->assigned);
- set_bit(SDW_BROADCAST_DEV_NUM, bus->assigned);
- /*
* SDW is an enumerable bus, but devices can be powered off. So,
* they won't be able to report as present.
*
* Create Slave devices based on Slaves described in
* the respective firmware (ACPI/DT)
*/
- if (IS_ENABLED(CONFIG_ACPI) && ACPI_HANDLE(bus->dev))
ret = sdw_acpi_find_slaves(bus);
- else
ret = -ENOTSUPP; /* No ACPI/DT so error out */
- if (ret) {
dev_err(bus->dev, "Finding slaves failed:%d\n", ret);
return ret;
- }
- return 0;
+} +EXPORT_SYMBOL(sdw_add_bus_master);
+static int sdw_delete_slave(struct device *dev, void *data) +{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- struct sdw_bus *bus = slave->bus;
- mutex_lock(&bus->bus_lock);
- if (slave->dev_num) /* clear dev_num if assigned */
clear_bit(slave->dev_num, bus->assigned);
- list_del_init(&slave->node);
- mutex_unlock(&bus->bus_lock);
- device_unregister(dev);
- return 0;
+}
+/**
- sdw_delete_bus_master() - delete the bus master instance
- @bus: bus to be deleted
- Remove the instance, delete the child devices.
- */
+void sdw_delete_bus_master(struct sdw_bus *bus) +{
- device_for_each_child(bus->dev, NULL, sdw_delete_slave);
+} +EXPORT_SYMBOL(sdw_delete_bus_master);
+void sdw_extract_slave_id(struct sdw_bus *bus,
u64 addr, struct sdw_slave_id *id)
+{
- dev_dbg(bus->dev, "SDW Slave Addr: %llx", addr);
- /*
* Spec definition
* Register Bit Contents
* DevId_0 [7:4] 47:44 sdw_version
* DevId_0 [3:0] 43:40 unique_id
* DevId_1 39:32 mfg_id [15:8]
* DevId_2 31:24 mfg_id [7:0]
Add a reference to mid.mipi.org (here or in the summary) ?
* DevId_3 23:16 part_id [15:8]
* DevId_4 15:08 part_id [7:0]
* DevId_5 07:00 class_id
*/
- id->sdw_version = (addr >> 44) & GENMASK(3, 0);
- id->unique_id = (addr >> 40) & GENMASK(3, 0);
- id->mfg_id = (addr >> 24) & GENMASK(15, 0);
- id->part_id = (addr >> 8) & GENMASK(15, 0);
- id->class_id = addr & GENMASK(7, 0);
- dev_dbg(bus->dev,
"SDW Slave class_id %x, part_id %x, mfg_id %x, unique_id %x, version %x",
id->class_id, id->part_id, id->mfg_id,
id->unique_id, id->sdw_version);
+} diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h new file mode 100644 index 000000000000..a54921825ce0 --- /dev/null +++ b/drivers/soundwire/bus.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation.
+#ifndef __SDW_BUS_H +#define __SDW_BUS_H
+#if IS_ENABLED(CONFIG_ACPI) +int sdw_acpi_find_slaves(struct sdw_bus *bus); +#else +static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) +{
- return -ENOTSUPP;
+} +#endif
+void sdw_extract_slave_id(struct sdw_bus *bus,
u64 addr, struct sdw_slave_id *id);
+#endif /* __SDW_BUS_H */ diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c new file mode 100644 index 000000000000..397e100d66e3 --- /dev/null +++ b/drivers/soundwire/slave.c @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation.
+#include <linux/acpi.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include "bus.h"
+static void sdw_slave_release(struct device *dev) +{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- kfree(slave);
+}
+static int sdw_slave_add(struct sdw_bus *bus,
struct sdw_slave_id *id, struct fwnode_handle *fwnode)
+{
- struct sdw_slave *slave;
- int ret;
- slave = kzalloc(sizeof(*slave), GFP_KERNEL);
- if (!slave)
return -ENOMEM;
- /* Initialize data structure */
- memcpy(&slave->id, id, sizeof(*id));
- slave->dev.parent = bus->dev;
- slave->dev.fwnode = fwnode;
- /* name shall be sdw:link:mfg:part:class:unique */
- dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x",
bus->link_id, id->mfg_id, id->part_id,
id->class_id, id->unique_id);
- slave->dev.release = sdw_slave_release;
- slave->dev.bus = &sdw_bus_type;
- slave->bus = bus;
- slave->status = SDW_SLAVE_UNATTACHED;
- slave->dev_num = 0;
- mutex_lock(&bus->bus_lock);
- list_add_tail(&slave->node, &bus->slaves);
- mutex_unlock(&bus->bus_lock);
- ret = device_register(&slave->dev);
- if (ret) {
dev_err(bus->dev, "Failed to add slave: ret %d\n", ret);
/*
* On err, don't free but drop ref as this will be freed
* when release method is invoked.
*/
mutex_lock(&bus->bus_lock);
list_del(&slave->node);
mutex_unlock(&bus->bus_lock);
put_device(&slave->dev);
- }
- return ret;
+}
+#if IS_ENABLED(CONFIG_ACPI) +/*
- sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node
- @bus: SDW bus instance
- Scans Master ACPI node for SDW child Slave devices and registers it.
- */
+int sdw_acpi_find_slaves(struct sdw_bus *bus) +{
- struct acpi_device *adev, *parent;
- parent = ACPI_COMPANION(bus->dev);
- if (!parent) {
dev_err(bus->dev, "Can't find parent for acpi bind\n");
return -ENODEV;
- }
- list_for_each_entry(adev, &parent->children, node) {
unsigned long long addr;
struct sdw_slave_id id;
unsigned int link_id;
acpi_status status;
status = acpi_evaluate_integer(adev->handle,
METHOD_NAME__ADR, NULL, &addr);
if (ACPI_FAILURE(status)) {
dev_err(bus->dev, "_ADR resolution failed: %x\n",
status);
return status;
}
/* Extract link id from ADR, it is from 48 to 51 bits */
nitpick: in bits 51..48 as defined in the MIPI SoundWire DisCo spec.
link_id = (addr >> 48) & GENMASK(3, 0);
/* Check for link_id match */
if (link_id != bus->link_id)
continue;
sdw_extract_slave_id(bus, addr, &id);
/*
* don't error check for sdw_slave_add as we want to continue
* adding Slaves
*/
sdw_slave_add(bus, &id, acpi_fwnode_handle(adev));
- }
- return 0;
+}
+#endif diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 4a7503781602..7e9579941c66 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -7,6 +7,14 @@ struct sdw_bus; struct sdw_slave;
+/* SDW spec defines and enums, as defined by MIPI 1.1. Spec */
+/* SDW Broadcast Device Number */ +#define SDW_BROADCAST_DEV_NUM 15
+/* SDW Enumeration Device Number */ +#define SDW_ENUM_DEV_NUM 0
add SDW_GROUP12_DEV_NUM 12 SDW_GROUP12_DEV_NUM 13 SDW_MASTER_DEV_NUM 14 /* not supported in these patches */
#define SDW_MAX_DEVICES 11
/**
@@ -108,4 +116,7 @@ struct sdw_bus { struct mutex bus_lock; };
+int sdw_add_bus_master(struct sdw_bus *bus); +void sdw_delete_bus_master(struct sdw_bus *bus);
- #endif /* __SOUNDWIRE_H */
On Fri, Dec 01, 2017 at 04:10:55PM -0600, Pierre-Louis Bossart wrote:
On 12/1/17 3:56 AM, Vinod Koul wrote:
A Master registers with SoundWire bus and scans the firmware provided
nitpick: is the 'register' correct? You create a bus instance for each hardware master interface. Or is my brain fried?
naah, thankfully it is not :)
I will reword this, I see it can cause ambguity :)
+int sdw_add_bus_master(struct sdw_bus *bus) +{
- int ret;
- if (!bus->dev) {
pr_err("SoundWire bus has no device");
return -ENODEV;
- }
- mutex_init(&bus->bus_lock);
- INIT_LIST_HEAD(&bus->slaves);
- /*
* Device numbers in SoundWire are 0 thru 15 with 0 being
* Enumeration device number and 15 broadcast device number. So
* they are not used for assignment so mask these and other
* higher bits
device 14 is used for the master and is not supported by these patches. While allowed, if you use device 12 and 13 (groups) you can't get the status information in a PING command so the recommendation is to avoid them. Those device numbers should never be used really (on top of 0 and 15 as explained above)
Yeah we should also exclude 12 thru 14 here. The group alllocation when done need to do differently that this one, so will mask those too.
+void sdw_extract_slave_id(struct sdw_bus *bus,
u64 addr, struct sdw_slave_id *id)
+{
- dev_dbg(bus->dev, "SDW Slave Addr: %llx", addr);
- /*
* Spec definition
* Register Bit Contents
* DevId_0 [7:4] 47:44 sdw_version
* DevId_0 [3:0] 43:40 unique_id
* DevId_1 39:32 mfg_id [15:8]
* DevId_2 31:24 mfg_id [7:0]
Add a reference to mid.mipi.org (here or in the summary) ?
Will add in summary, makes sense on that page
+int sdw_acpi_find_slaves(struct sdw_bus *bus) +{
- struct acpi_device *adev, *parent;
- parent = ACPI_COMPANION(bus->dev);
- if (!parent) {
dev_err(bus->dev, "Can't find parent for acpi bind\n");
return -ENODEV;
- }
- list_for_each_entry(adev, &parent->children, node) {
unsigned long long addr;
struct sdw_slave_id id;
unsigned int link_id;
acpi_status status;
status = acpi_evaluate_integer(adev->handle,
METHOD_NAME__ADR, NULL, &addr);
if (ACPI_FAILURE(status)) {
dev_err(bus->dev, "_ADR resolution failed: %x\n",
status);
return status;
}
/* Extract link id from ADR, it is from 48 to 51 bits */
nitpick: in bits 51..48 as defined in the MIPI SoundWire DisCo spec.
ok
+/* SDW Enumeration Device Number */ +#define SDW_ENUM_DEV_NUM 0
add SDW_GROUP12_DEV_NUM 12 SDW_GROUP12_DEV_NUM 13
you mean SDW_GROUP13_DEV_NUM :D
SDW_MASTER_DEV_NUM 14 /* not supported in these patches */
These dont help now as we dont support, so kind of dead code. Lets add them when we really support it
+/* SDW Enumeration Device Number */ +#define SDW_ENUM_DEV_NUM 0
add SDW_GROUP12_DEV_NUM 12 SDW_GROUP12_DEV_NUM 13
you mean SDW_GROUP13_DEV_NUM :D
SDW_MASTER_DEV_NUM 14 /* not supported in these patches */
These dont help now as we dont support, so kind of dead code. Lets add them when we really support it
It was a way to explain why device 14 is never allocated.
On Sun, Dec 03, 2017 at 08:44:58PM -0600, Pierre-Louis Bossart wrote:
+/* SDW Enumeration Device Number */ +#define SDW_ENUM_DEV_NUM 0
add SDW_GROUP12_DEV_NUM 12 SDW_GROUP12_DEV_NUM 13
you mean SDW_GROUP13_DEV_NUM :D
SDW_MASTER_DEV_NUM 14 /* not supported in these patches */
These dont help now as we dont support, so kind of dead code. Lets add them when we really support it
It was a way to explain why device 14 is never allocated.
Rethinking about it, i need these defines anyway for masking them out now, so will add
MIPI Discovery And Configuration (DisCo) Specification for SoundWire specifies properties to be implemented for SoundWire Masters and Slaves. The DisCo spec doesn't mandate these properties. However, SDW bus cannot work without knowing these values.
The helper functions read the Master and Slave properties. Implementers of Master or Slave drivers can use any of the below three mechanisms: a) Use these APIs here as .read_prop() callback for Master and Slave b) Implement own methods and set those as .read_prop(), but invoke APIs in this file for generic read and override the values with platform specific data c) Implement ones own methods which do not use anything provided here
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 8 + drivers/soundwire/bus_type.c | 23 ++- drivers/soundwire/mipi_disco.c | 374 +++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 274 ++++++++++++++++++++++++++++++ 5 files changed, 679 insertions(+), 2 deletions(-) create mode 100644 drivers/soundwire/mipi_disco.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index c875e434f8b3..bcde0d26524c 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -3,5 +3,5 @@ #
#Bus Objs -soundwire-bus-objs := bus_type.o bus.o slave.o +soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 0f89b2f36938..507ae85ad58e 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -25,6 +25,14 @@ int sdw_add_bus_master(struct sdw_bus *bus) mutex_init(&bus->bus_lock); INIT_LIST_HEAD(&bus->slaves);
+ if (bus->ops->read_prop) { + ret = bus->ops->read_prop(bus); + if (ret < 0) { + dev_err(bus->dev, "Bus read properties failed:%d", ret); + return ret; + } + } + /* * Device numbers in SoundWire are 0 thru 15 with 0 being * Enumeration device number and 15 broadcast device number. So diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index 8d8dcc68e9a8..d5f3a70c06b0 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -77,6 +77,8 @@ static int sdw_drv_probe(struct device *dev) if (!id) return -ENODEV;
+ slave->ops = drv->ops; + /* * attach to power domain but don't turn on (last arg) */ @@ -89,7 +91,26 @@ static int sdw_drv_probe(struct device *dev) } }
- return ret; + if (ret) + return ret; + + /* device is probed so let's read the properties now */ + if (slave->ops && slave->ops->read_prop) + slave->ops->read_prop(slave); + + /* + * Check for valid clk_stop_timeout, use DisCo worst case value of + * 300ms + * + * TODO: check the timeouts and driver removal case + */ + if (slave->prop.clk_stop_timeout == 0) + slave->prop.clk_stop_timeout = 300; + + slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout, + slave->prop.clk_stop_timeout); + + return 0; }
static int sdw_drv_remove(struct device *dev) diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c new file mode 100644 index 000000000000..b8870d4a25ba --- /dev/null +++ b/drivers/soundwire/mipi_disco.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * MIPI Discovery And Configuration (DisCo) Specification for SoundWire + * specifies properties to be implemented for SoundWire Masters and Slaves. + * The DisCo spec doesn't mandate these properties. However, SDW bus cannot + * work without knowing these values. + * + * The helper functions read the Master and Slave properties. Implementers + * of Master or Slave drivers can use any of the below three mechanisms: + * a) Use these APIs here as .read_prop() callback for Master and Slave + * b) Implement own methods and set those as .read_prop(), but invoke + * APIs in this file for generic read and override the values with + * platform specific data + * c) Implement ones own methods which do not use anything provided + * here + */ + +#include <linux/device.h> +#include <linux/property.h> +#include <linux/mod_devicetable.h> +#include <linux/soundwire/sdw.h> +#include "bus.h" + +/** + * sdw_master_read_prop() - Read Master properties + * @bus: SDW bus instance + */ +int sdw_master_read_prop(struct sdw_bus *bus) +{ + struct sdw_master_prop *prop = &bus->prop; + struct fwnode_handle *link; + unsigned int count = 0; + char name[32]; + int nval, i; + + device_property_read_u32(bus->dev, + "mipi-sdw-sw-interface-revision", &prop->revision); + device_property_read_u32(bus->dev, "mipi-sdw-master-count", &count); + + /* Find link handle */ + snprintf(name, sizeof(name), + "mipi-sdw-link-%d-subproperties", bus->link_id); + + link = device_get_named_child_node(bus->dev, name); + if (!link) { + dev_err(bus->dev, "Link node %s not found\n", name); + return -EIO; + } + + if (fwnode_property_read_bool(link, + "mipi-sdw-clock-stop-mode0-supported") == true) + prop->clk_stop_mode = SDW_CLK_STOP_MODE0; + + if (fwnode_property_read_bool(link, + "mipi-sdw-clock-stop-mode1-supported") == true) + prop->clk_stop_mode |= SDW_CLK_STOP_MODE1; + + fwnode_property_read_u32(link, + "mipi-sdw-max-clock-frequency", &prop->max_freq); + + nval = fwnode_property_read_u32_array(link, + "mipi-sdw-clock-frequencies-supported", NULL, 0); + if (nval > 0) + prop->num_freq = nval; + + if (prop->num_freq) { + prop->freq = devm_kcalloc(bus->dev, nval, + sizeof(*prop->freq), GFP_KERNEL); + if (!prop->freq) + return -ENOMEM; + + fwnode_property_read_u32_array(link, + "mipi-sdw-clock-frequencies-supported", + prop->freq, nval); + + /* + * Check the frequencies supported. If FW doesn't provide max + * freq, then populate here by checking values. + */ + if (!prop->max_freq) { + prop->max_freq = prop->freq[0]; + for (i = 1; i < prop->num_freq; i++) { + if (prop->freq[i] > prop->max_freq) + prop->max_freq = prop->freq[i]; + } + } + } + + nval = fwnode_property_read_u32_array(link, + "mipi-sdw-supported-clock-gears", NULL, 0); + if (nval > 0) + prop->num_clk_gears = nval; + + if (prop->num_clk_gears) { + prop->clk_gears = devm_kcalloc(bus->dev, nval, + sizeof(*prop->clk_gears), GFP_KERNEL); + if (!prop->clk_gears) + return -ENOMEM; + + fwnode_property_read_u32_array(link, + "mipi-sdw-supported-clock-gears", + prop->clk_gears, nval); + } + + fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate", + &prop->default_frame_rate); + fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size", + &prop->default_row); + fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size", + &prop->default_col); + prop->dynamic_frame = fwnode_property_read_bool(link, + "mipi-sdw-dynamic-frame-shape"); + fwnode_property_read_u32(link, "mipi-sdw-command-error-threshold", + &prop->err_threshold); + + return 0; +} +EXPORT_SYMBOL(sdw_master_read_prop); + +static int sdw_slave_read_dpn(struct sdw_slave *slave, + struct sdw_dpn_prop *dpn, int count, int ports, char *type) +{ + struct fwnode_handle *node; + u32 bit, i = 0, nval; + unsigned long addr; + char name[40]; + + addr = ports; + /* valid ports are 1 to 14 so apply mask */ + addr &= GENMASK(14, 1); + + for_each_set_bit(bit, &addr, 32) { + snprintf(name, sizeof(name), + "mipi-sdw-dp-%d-%s-subproperties", bit, type); + + dpn[i].num = bit; + + node = device_get_named_child_node(&slave->dev, name); + if (!node) { + dev_err(&slave->dev, "%s dpN not found\n", name); + return -EIO; + } + + fwnode_property_read_u32(node, "mipi-sdw-port-max-wordlength", + &dpn[i].max_word); + fwnode_property_read_u32(node, "mipi-sdw-port-min-wordlength", + &dpn[i].min_word); + + nval = fwnode_property_read_u32_array(node, + "mipi-sdw-port-wordlength-configs", NULL, 0); + if (nval > 0) + dpn[i].num_words = nval; + + if (dpn[i].num_words) { + dpn[i].words = devm_kcalloc(&slave->dev, nval, + sizeof(*dpn[i].words), GFP_KERNEL); + if (!dpn[i].words) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-port-wordlength-configs", + &dpn[i].num_words, nval); + } + + fwnode_property_read_u32(node, "mipi-sdw-data-port-type", + &dpn[i].type); + fwnode_property_read_u32(node, + "mipi-sdw-max-grouping-supported", + &dpn[i].max_grouping); + dpn[i].simple_ch_prep_sm = fwnode_property_read_bool(node, + "mipi-sdw-simplified-channelprepare-sm"); + fwnode_property_read_u32(node, + "mipi-sdw-port-channelprepare-timeout", + &dpn[i].ch_prep_timeout); + fwnode_property_read_u32(node, + "mipi-sdw-imp-def-dpn-interrupts-supported", + &dpn[i].device_interrupts); + fwnode_property_read_u32(node, "mipi-sdw-min-channel-number", + &dpn[i].min_ch); + fwnode_property_read_u32(node, "mipi-sdw-max-channel-number", + &dpn[i].max_ch); + + nval = fwnode_property_read_u32_array(node, + "mipi-sdw-channel-number-list", NULL, 0); + if (nval > 0) + dpn[i].num_ch = nval; + + if (dpn[i].num_ch) { + dpn[i].ch = devm_kcalloc(&slave->dev, nval, + sizeof(*dpn[i].ch), GFP_KERNEL); + if (!dpn[i].ch) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-channel-number-list", + dpn[i].ch, nval); + } + + nval = fwnode_property_read_u32_array(node, + "mipi-sdw-channel-combination-list", NULL, 0); + if (nval > 0) + dpn[i].num_ch_combinations = nval; + + if (dpn[i].num_ch_combinations) { + dpn[i].ch_combinations = devm_kcalloc(&slave->dev, + nval, sizeof(*dpn[i].ch_combinations), + GFP_KERNEL); + if (!dpn[i].ch_combinations) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-channel-combination-list", + dpn[i].ch_combinations, nval); + } + + fwnode_property_read_u32(node, + "mipi-sdw-modes-supported", &dpn[i].modes); + fwnode_property_read_u32(node, "mipi-sdw-max-async-buffer", + &dpn[i].max_async_buffer); + dpn[i].block_pack_mode = fwnode_property_read_bool(node, + "mipi-sdw-block-packing-mode"); + + fwnode_property_read_u32(node, "mipi-sdw-port-encoding-type", + &dpn[i].port_encoding); + + /* TODO: Read audio mode */ + + i++; + } + + return 0; +} + +/** + * sdw_slave_read_prop() - Read Slave properties + * @slave: SDW Slave + */ +int sdw_slave_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct device *dev = &slave->dev; + struct fwnode_handle *port; + int num_of_ports, nval, i; + + device_property_read_u32(dev, "mipi-sdw-sw-interface-revision", + &prop->mipi_revision); + + prop->wake_capable = device_property_read_bool(dev, + "mipi-sdw-wake-up-unavailable"); + prop->wake_capable = !prop->wake_capable; + + prop->test_mode_capable = device_property_read_bool(dev, + "mipi-sdw-test-mode-supported"); + + prop->clk_stop_mode1 = false; + if (device_property_read_bool(dev, + "mipi-sdw-clock-stop-mode1-supported")) + prop->clk_stop_mode1 = true; + + prop->simple_clk_stop_capable = device_property_read_bool(dev, + "mipi-sdw-simplified-clockstopprepare-sm-supported"); + + device_property_read_u32(dev, "mipi-sdw-clockstopprepare-timeout", + &prop->clk_stop_timeout); + device_property_read_u32(dev, "mipi-sdw-slave-channelprepare-timeout", + &prop->ch_prep_timeout); + device_property_read_u32(dev, + "mipi-sdw-clockstopprepare-hard-reset-behavior", + &prop->reset_behave); + + prop->high_PHY_capable = device_property_read_bool(dev, + "mipi-sdw-highPHY-capable"); + prop->paging_support = device_property_read_bool(dev, + "mipi-sdw-paging-support"); + prop->bank_delay_support = device_property_read_bool(dev, + "mipi-sdw-bank-delay-support"); + device_property_read_u32(dev, + "mipi-sdw-port15-read-behavior", &prop->p15_behave); + device_property_read_u32(dev, "mipi-sdw-master-count", + &prop->master_count); + + device_property_read_u32(dev, "mipi-sdw-source-port-list", + &prop->source_ports); + device_property_read_u32(dev, "mipi-sdw-sink-port-list", + &prop->sink_ports); + + /* Read dp0 properties */ + port = device_get_named_child_node(dev, "mipi-sdw-dp-0-subproperties"); + if (!port) { + dev_err(dev, "DP0 node not found!!\n"); + return -EIO; + } + + prop->dp0_prop = devm_kzalloc(&slave->dev, + sizeof(*prop->dp0_prop), GFP_KERNEL); + if (!prop->dp0_prop) + return -ENOMEM; + + fwnode_property_read_u32(port, "mipi-sdw-port-max-wordlength", + &prop->dp0_prop->max_word); + fwnode_property_read_u32(port, "mipi-sdw-port-min-wordlength", + &prop->dp0_prop->min_word); + nval = fwnode_property_read_u32_array(port, + "mipi-sdw-port-wordlength-configs", NULL, 0); + if (nval > 0) + prop->dp0_prop->num_words = nval; + + if (prop->dp0_prop->num_words) { + prop->dp0_prop->words = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->dp0_prop->words), GFP_KERNEL); + if (!prop->dp0_prop->words) + return -ENOMEM; + + fwnode_property_read_u32_array(port, + "mipi-sdw-port-wordlength-configs", + prop->dp0_prop->words, nval); + } + + prop->dp0_prop->flow_controlled = fwnode_property_read_bool(port, + "mipi-sdw-bra-flow-controlled"); + + prop->dp0_prop->simple_ch_prep_sm = fwnode_property_read_bool(port, + "mipi-sdw-simplified-channel-prepare-sm"); + + prop->dp0_prop->device_interrupts = fwnode_property_read_bool(port, + "mipi-sdw-imp-def-dp0-interrupts-supported"); + + /* + * Based on each DPn port, get source and sink dpn properties. + * Also, some ports can operate as both source or sink. + */ + + /* Allocate memory for set bits in port lists */ + nval = hweight32(prop->source_ports); + num_of_ports += nval; + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + + /* Read dpn properties for source port(s) */ + sdw_slave_read_dpn(slave, prop->src_dpn_prop, nval, + prop->source_ports, "source"); + + nval = hweight32(prop->sink_ports); + num_of_ports += nval; + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + /* Read dpn properties for sink port(s) */ + sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval, + prop->sink_ports, "sink"); + + /* some ports are bidirectional so check total ports by ORing */ + nval = prop->source_ports | prop->sink_ports; + num_of_ports = hweight32(nval) + 1; /* add 1 for DP0 */ + + /* Allocate port_ready based on num_of_ports */ + slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports, + sizeof(*slave->port_ready), GFP_KERNEL); + if (!slave->port_ready) + return -ENOMEM; + + /* Initialize completion */ + for (i = 0; i < num_of_ports; i++) + init_completion(&slave->port_ready[i]); + + return 0; +} +EXPORT_SYMBOL(sdw_slave_read_prop); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 7e9579941c66..ddfae18f4306 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -32,6 +32,252 @@ enum sdw_slave_status { };
/* + * SDW properties, defined in MIPI DisCo spec v1.0 + */ +enum sdw_clk_stop_reset_behave { + SDW_CLK_STOP_KEEP_STATUS = 1, +}; + +/** + * enum sdw_p15_behave - Slave Port 15 behaviour when the Master attempts a + * read + * @SDW_P15_READ_IGNORED: Read is ignored + * @SDW_P15_CMD_OK: Command is ok + */ +enum sdw_p15_behave { + SDW_P15_READ_IGNORED = 0, + SDW_P15_CMD_OK = 1, +}; + +/** + * enum sdw_dpn_type - Data port types + * @SDW_DPN_FULL: Full Data Port is supported + * @SDW_DPN_SIMPLE: Simplified Data Port as defined in spec. + * DPN_SampleCtrl2, DPN_OffsetCtrl2, DPN_HCtrl and DPN_BlockCtrl3 + * are not implemented. + * @SDW_DPN_REDUCED: Reduced Data Port as defined in spec. + * DPN_SampleCtrl2, DPN_HCtrl are not implemented. + */ +enum sdw_dpn_type { + SDW_DPN_FULL = 0, + SDW_DPN_SIMPLE = 1, + SDW_DPN_REDUCED = 2, +}; + +/** + * enum sdw_clk_stop_mode - Clock Stop modes + * @SDW_CLK_STOP_MODE0: Slave can continue operation seamlessly on clock + * restart + * @SDW_CLK_STOP_MODE1: Slave may have entered a deeper power-saving mode, + * not capable of continuing operation seamlessly when the clock restarts + */ +enum sdw_clk_stop_mode { + SDW_CLK_STOP_MODE0 = 1, + SDW_CLK_STOP_MODE1 = 2, +}; + +/** + * struct sdw_dp0_prop - DP0 properties + * @max_word: Maximum number of bits in a Payload Channel Sample, 1 to 64 + * (inclusive) + * @min_word: Minimum number of bits in a Payload Channel Sample, 1 to 64 + * (inclusive) + * @num_words: number of wordlengths supported + * @words: wordlengths supported + * @flow_controlled: Slave implementation results in an OK_NotReady + * response + * @simple_ch_prep_sm: If channel prepare sequence is required + * @device_interrupts: If implementation-defined interrupts are supported + * + * The wordlengths are specified by Spec as max, min AND number of + * discrete values, implementation can define based on the wordlengths they + * support + */ +struct sdw_dp0_prop { + u32 max_word; + u32 min_word; + u32 num_words; + u32 *words; + bool flow_controlled; + bool simple_ch_prep_sm; + bool device_interrupts; +}; + +/** + * struct sdw_dpn_audio_mode - Audio mode properties for DPn + * @bus_min_freq: Minimum bus frequency, in Hz + * @bus_max_freq: Maximum bus frequency, in Hz + * @bus_num_freq: Number of discrete frequencies supported + * @bus_freq: Discrete bus frequencies, in Hz + * @min_freq: Minimum sampling frequency, in Hz + * @max_freq: Maximum sampling bus frequency, in Hz + * @num_freq: Number of discrete sampling frequency supported + * @freq: Discrete sampling frequencies, in Hz + * @prep_ch_behave: Specifies the dependencies between Channel Prepare + * sequence and bus clock configuration + * If 0, Channel Prepare can happen at any Bus clock rate + * If 1, Channel Prepare sequence shall happen only after Bus clock is + * changed to a frequency supported by this mode or compatible modes + * described by the next field + * @glitchless: Bitmap describing possible glitchless transitions from this + * Audio Mode to other Audio Modes + */ +struct sdw_dpn_audio_mode { + u32 bus_min_freq; + u32 bus_max_freq; + u32 bus_num_freq; + u32 *bus_freq; + u32 max_freq; + u32 min_freq; + u32 num_freq; + u32 *freq; + u32 prep_ch_behave; + u32 glitchless; +}; + +/** + * struct sdw_dpn_prop - Data Port DPn properties + * @num: port number + * @max_word: Maximum number of bits in a Payload Channel Sample, 1 to 64 + * (inclusive) + * @min_word: Minimum number of bits in a Payload Channel Sample, 1 to 64 + * (inclusive) + * @num_words: Number of discrete supported wordlengths + * @words: Discrete supported wordlength + * @type: Data port type. Full, Simplified or Reduced + * @max_grouping: Maximum number of samples that can be grouped together for + * a full data port + * @simple_ch_prep_sm: If the port supports simplified channel prepare state + * machine + * @ch_prep_timeout: Port-specific timeout value, in milliseconds + * @device_interrupts: If set, each bit corresponds to support for + * implementation-defined interrupts + * @max_ch: Maximum channels supported + * @min_ch: Minimum channels supported + * @num_ch: Number of discrete channels supported + * @ch: Discrete channels supported + * @num_ch_combinations: Number of channel combinations supported + * @ch_combinations: Channel combinations supported + * @modes: SDW mode supported + * @max_async_buffer: Number of samples that this port can buffer in + * asynchronous modes + * @block_pack_mode: Type of block port mode supported + * @port_encoding: Payload Channel Sample encoding schemes supported + * @audio_modes: Audio modes supported + */ +struct sdw_dpn_prop { + u32 num; + u32 max_word; + u32 min_word; + u32 num_words; + u32 *words; + enum sdw_dpn_type type; + u32 max_grouping; + bool simple_ch_prep_sm; + u32 ch_prep_timeout; + u32 device_interrupts; + u32 max_ch; + u32 min_ch; + u32 num_ch; + u32 *ch; + u32 num_ch_combinations; + u32 *ch_combinations; + u32 modes; + u32 max_async_buffer; + bool block_pack_mode; + u32 port_encoding; + struct sdw_dpn_audio_mode *audio_modes; +}; + +/** + * struct sdw_slave_prop - SoundWire Slave properties + * @mipi_revision: Spec version of the implementation + * @wake_capable: Wake-up events are supported + * @test_mode_capable: If test mode is supported + * @clk_stop_mode1: Clock-Stop Mode 1 is supported + * @simple_clk_stop_capable: Simple clock mode is supported + * @clk_stop_timeout: Worst-case latency of the Clock Stop Prepare State + * Machine transitions, in milliseconds + * @ch_prep_timeout: Worst-case latency of the Channel Prepare State Machine + * transitions, in milliseconds + * @reset_behave: Slave keeps the status of the SlaveStopClockPrepare + * state machine (P=1 SCSP_SM) after exit from clock-stop mode1 + * @high_PHY_capable: Slave is HighPHY capable + * @paging_support: Slave implements paging registers SCP_AddrPage1 and + * SCP_AddrPage2 + * @bank_delay_support: Slave implements bank delay/bridge support registers + * SCP_BankDelay and SCP_NextFrame + * @p15_behave: Slave behavior when the Master attempts a read to the Port15 + * alias + * @lane_control_support: Slave supports lane control + * @master_count: Number of Masters present on this Slave + * @source_ports: Bitmap identifying source ports + * @sink_ports: Bitmap identifying sink ports + * @dp0_prop: Data Port 0 properties + * @src_dpn_prop: Source Data Port N properties + * @sink_dpn_prop: Sink Data Port N properties + */ +struct sdw_slave_prop { + u32 mipi_revision; + bool wake_capable; + bool test_mode_capable; + bool clk_stop_mode1; + bool simple_clk_stop_capable; + u32 clk_stop_timeout; + u32 ch_prep_timeout; + enum sdw_clk_stop_reset_behave reset_behave; + bool high_PHY_capable; + bool paging_support; + bool bank_delay_support; + enum sdw_p15_behave p15_behave; + bool lane_control_support; + u32 master_count; + u32 source_ports; + u32 sink_ports; + struct sdw_dp0_prop *dp0_prop; + struct sdw_dpn_prop *src_dpn_prop; + struct sdw_dpn_prop *sink_dpn_prop; +}; + +/** + * struct sdw_master_prop - Master properties + * @revision: MIPI spec version of the implementation + * @master_count: Number of masters + * @clk_stop_mode: Bitmap for Clock Stop modes supported + * @max_freq: Maximum Bus clock frequency, in Hz + * @num_clk_gears: Number of clock gears supported + * @clk_gears: Clock gears supported + * @num_freq: Number of clock frequencies supported, in Hz + * @freq: Clock frequencies supported, in Hz + * @default_frame_rate: Controller default Frame rate, in Hz + * @default_row: Number of rows + * @default_col: Number of columns + * @dynamic_frame: Dynamic frame supported + * @err_threshold: Number of times that software may retry sending a single + * command + * @dpn_prop: Data Port N properties + */ +struct sdw_master_prop { + u32 revision; + u32 master_count; + enum sdw_clk_stop_mode clk_stop_mode; + u32 max_freq; + u32 num_clk_gears; + u32 *clk_gears; + u32 num_freq; + u32 *freq; + u32 default_frame_rate; + u32 default_row; + u32 default_col; + bool dynamic_frame; + u32 err_threshold; + struct sdw_dpn_prop *dpn_prop; +}; + +int sdw_master_read_prop(struct sdw_bus *bus); +int sdw_slave_read_prop(struct sdw_slave *slave); + +/* * SDW Slave Structures and APIs */
@@ -55,12 +301,23 @@ struct sdw_slave_id { };
/** + * struct sdw_slave_ops - Slave driver callback ops + * @read_prop: Read Slave properties + */ +struct sdw_slave_ops { + int (*read_prop)(struct sdw_slave *sdw); +}; + +/** * struct sdw_slave - SoundWire Slave * @id: MIPI device ID * @dev: Linux device * @status: Status reported by the Slave * @bus: Bus handle + * @ops: Slave callback ops + * @prop: Slave properties * @node: node for bus list + * @port_ready: Port ready completion flag for each Slave port * @dev_num: Device Number assigned by Bus */ struct sdw_slave { @@ -68,7 +325,10 @@ struct sdw_slave { struct device dev; enum sdw_slave_status status; struct sdw_bus *bus; + const struct sdw_slave_ops *ops; + struct sdw_slave_prop prop; struct list_head node; + struct completion *port_ready; u16 dev_num; };
@@ -100,6 +360,14 @@ int sdw_handle_slave_status(struct sdw_bus *bus, */
/** + * struct sdw_master_ops - Master driver ops + * @read_prop: Read Master properties + */ +struct sdw_master_ops { + int (*read_prop)(struct sdw_bus *bus); +}; + +/** * struct sdw_bus - SoundWire bus * @dev: Master linux device * @link_id: Link id number, can be 0 to N, unique for each Master @@ -107,6 +375,9 @@ int sdw_handle_slave_status(struct sdw_bus *bus, * @assigned: Bitmap for Slave device numbers. * Bit set implies used number, bit clear implies unused number. * @bus_lock: bus lock + * @ops: Master callback ops + * @prop: Master properties + * @clk_stop_timeout: Clock stop timeout computed */ struct sdw_bus { struct device *dev; @@ -114,6 +385,9 @@ struct sdw_bus { struct list_head slaves; DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); struct mutex bus_lock; + const struct sdw_master_ops *ops; + struct sdw_master_prop prop; + unsigned int clk_stop_timeout; };
int sdw_add_bus_master(struct sdw_bus *bus);
On 12/1/17 3:56 AM, Vinod Koul wrote:
MIPI Discovery And Configuration (DisCo) Specification for SoundWire specifies properties to be implemented for SoundWire Masters and Slaves. The DisCo spec doesn't mandate these properties. However, SDW bus cannot work without knowing these values.
The helper functions read the Master and Slave properties. Implementers of Master or Slave drivers can use any of the below three mechanisms: a) Use these APIs here as .read_prop() callback for Master and Slave b) Implement own methods and set those as .read_prop(), but invoke APIs in this file for generic read and override the values with platform specific data c) Implement ones own methods which do not use anything provided here
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 8 + drivers/soundwire/bus_type.c | 23 ++- drivers/soundwire/mipi_disco.c | 374 +++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 274 ++++++++++++++++++++++++++++++ 5 files changed, 679 insertions(+), 2 deletions(-) create mode 100644 drivers/soundwire/mipi_disco.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index c875e434f8b3..bcde0d26524c 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -3,5 +3,5 @@ #
#Bus Objs -soundwire-bus-objs := bus_type.o bus.o slave.o +soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 0f89b2f36938..507ae85ad58e 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -25,6 +25,14 @@ int sdw_add_bus_master(struct sdw_bus *bus) mutex_init(&bus->bus_lock); INIT_LIST_HEAD(&bus->slaves);
- if (bus->ops->read_prop) {
ret = bus->ops->read_prop(bus);
if (ret < 0) {
dev_err(bus->dev, "Bus read properties failed:%d", ret);
return ret;
}
- }
- /*
- Device numbers in SoundWire are 0 thru 15 with 0 being
- Enumeration device number and 15 broadcast device number. So
diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index 8d8dcc68e9a8..d5f3a70c06b0 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -77,6 +77,8 @@ static int sdw_drv_probe(struct device *dev) if (!id) return -ENODEV;
- slave->ops = drv->ops;
- /*
*/
- attach to power domain but don't turn on (last arg)
@@ -89,7 +91,26 @@ static int sdw_drv_probe(struct device *dev) } }
- return ret;
if (ret)
return ret;
/* device is probed so let's read the properties now */
if (slave->ops && slave->ops->read_prop)
slave->ops->read_prop(slave);
/*
* Check for valid clk_stop_timeout, use DisCo worst case value of
* 300ms
*
* TODO: check the timeouts and driver removal case
*/
if (slave->prop.clk_stop_timeout == 0)
slave->prop.clk_stop_timeout = 300;
slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout,
slave->prop.clk_stop_timeout);
return 0; }
static int sdw_drv_remove(struct device *dev)
diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c new file mode 100644 index 000000000000..b8870d4a25ba --- /dev/null +++ b/drivers/soundwire/mipi_disco.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation.
+/*
- MIPI Discovery And Configuration (DisCo) Specification for SoundWire
- specifies properties to be implemented for SoundWire Masters and Slaves.
- The DisCo spec doesn't mandate these properties. However, SDW bus cannot
- work without knowing these values.
- The helper functions read the Master and Slave properties. Implementers
- of Master or Slave drivers can use any of the below three mechanisms:
- a) Use these APIs here as .read_prop() callback for Master and Slave
- b) Implement own methods and set those as .read_prop(), but invoke
- APIs in this file for generic read and override the values with
- platform specific data
- c) Implement ones own methods which do not use anything provided
- here
- */
+#include <linux/device.h> +#include <linux/property.h> +#include <linux/mod_devicetable.h> +#include <linux/soundwire/sdw.h> +#include "bus.h"
+/**
- sdw_master_read_prop() - Read Master properties
- @bus: SDW bus instance
- */
+int sdw_master_read_prop(struct sdw_bus *bus) +{
- struct sdw_master_prop *prop = &bus->prop;
- struct fwnode_handle *link;
- unsigned int count = 0;
- char name[32];
- int nval, i;
- device_property_read_u32(bus->dev,
"mipi-sdw-sw-interface-revision", &prop->revision);
- device_property_read_u32(bus->dev, "mipi-sdw-master-count", &count);
- /* Find link handle */
- snprintf(name, sizeof(name),
"mipi-sdw-link-%d-subproperties", bus->link_id);
if you follow the DisCo spec, this property is at the controller level, isn't there a confusion between controller/master here, and consequently are we reading the same things multiple times or using the wrong bus parameter?
If I look at intel_probe(), there is a clear reference to a link_id, and then you set the pointer to this read_prop which reads the number of links, which looks like the wrong order. You can't assign a link ID before knowing how many links there are - or you may be unable to detect issues.
- link = device_get_named_child_node(bus->dev, name);
- if (!link) {
dev_err(bus->dev, "Link node %s not found\n", name);
return -EIO;
- }
- if (fwnode_property_read_bool(link,
"mipi-sdw-clock-stop-mode0-supported") == true)
prop->clk_stop_mode = SDW_CLK_STOP_MODE0;
- if (fwnode_property_read_bool(link,
"mipi-sdw-clock-stop-mode1-supported") == true)
prop->clk_stop_mode |= SDW_CLK_STOP_MODE1;
- fwnode_property_read_u32(link,
"mipi-sdw-max-clock-frequency", &prop->max_freq);
- nval = fwnode_property_read_u32_array(link,
"mipi-sdw-clock-frequencies-supported", NULL, 0);
- if (nval > 0)
prop->num_freq = nval;
- if (prop->num_freq) {
prop->freq = devm_kcalloc(bus->dev, nval,
sizeof(*prop->freq), GFP_KERNEL);
if (!prop->freq)
return -ENOMEM;
fwnode_property_read_u32_array(link,
"mipi-sdw-clock-frequencies-supported",
prop->freq, nval);
/*
* Check the frequencies supported. If FW doesn't provide max
* freq, then populate here by checking values.
*/
if (!prop->max_freq) {
prop->max_freq = prop->freq[0];
for (i = 1; i < prop->num_freq; i++) {
if (prop->freq[i] > prop->max_freq)
prop->max_freq = prop->freq[i];
}
}
- }
- nval = fwnode_property_read_u32_array(link,
"mipi-sdw-supported-clock-gears", NULL, 0);
- if (nval > 0)
prop->num_clk_gears = nval;
- if (prop->num_clk_gears) {
prop->clk_gears = devm_kcalloc(bus->dev, nval,
sizeof(*prop->clk_gears), GFP_KERNEL);
if (!prop->clk_gears)
return -ENOMEM;
fwnode_property_read_u32_array(link,
"mipi-sdw-supported-clock-gears",
prop->clk_gears, nval);
- }
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate",
&prop->default_frame_rate);
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size",
&prop->default_row);
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size",
This is fine, just wondering if we should warnings if the values make no sense, e.g. the DisCo spec states in Note1 page 15 that the values are interrelated.
&prop->default_col);
- prop->dynamic_frame = fwnode_property_read_bool(link,
"mipi-sdw-dynamic-frame-shape");
- fwnode_property_read_u32(link, "mipi-sdw-command-error-threshold",
&prop->err_threshold);
- return 0;
+} +EXPORT_SYMBOL(sdw_master_read_prop);
+static int sdw_slave_read_dpn(struct sdw_slave *slave,
struct sdw_dpn_prop *dpn, int count, int ports, char *type)
+{
- struct fwnode_handle *node;
- u32 bit, i = 0, nval;
- unsigned long addr;
- char name[40];
- addr = ports;
- /* valid ports are 1 to 14 so apply mask */
- addr &= GENMASK(14, 1);
- for_each_set_bit(bit, &addr, 32) {
snprintf(name, sizeof(name),
"mipi-sdw-dp-%d-%s-subproperties", bit, type);
dpn[i].num = bit;
node = device_get_named_child_node(&slave->dev, name);
if (!node) {
dev_err(&slave->dev, "%s dpN not found\n", name);
return -EIO;
}
fwnode_property_read_u32(node, "mipi-sdw-port-max-wordlength",
&dpn[i].max_word);
fwnode_property_read_u32(node, "mipi-sdw-port-min-wordlength",
&dpn[i].min_word);
nval = fwnode_property_read_u32_array(node,
"mipi-sdw-port-wordlength-configs", NULL, 0);
if (nval > 0)
dpn[i].num_words = nval;
if (dpn[i].num_words) {
dpn[i].words = devm_kcalloc(&slave->dev, nval,
sizeof(*dpn[i].words), GFP_KERNEL);
if (!dpn[i].words)
return -ENOMEM;
fwnode_property_read_u32_array(node,
"mipi-sdw-port-wordlength-configs",
&dpn[i].num_words, nval);
}
fwnode_property_read_u32(node, "mipi-sdw-data-port-type",
&dpn[i].type);
fwnode_property_read_u32(node,
"mipi-sdw-max-grouping-supported",
&dpn[i].max_grouping);
dpn[i].simple_ch_prep_sm = fwnode_property_read_bool(node,
"mipi-sdw-simplified-channelprepare-sm");
fwnode_property_read_u32(node,
"mipi-sdw-port-channelprepare-timeout",
&dpn[i].ch_prep_timeout);
fwnode_property_read_u32(node,
"mipi-sdw-imp-def-dpn-interrupts-supported",
&dpn[i].device_interrupts);
fwnode_property_read_u32(node, "mipi-sdw-min-channel-number",
&dpn[i].min_ch);
fwnode_property_read_u32(node, "mipi-sdw-max-channel-number",
&dpn[i].max_ch);
nval = fwnode_property_read_u32_array(node,
"mipi-sdw-channel-number-list", NULL, 0);
if (nval > 0)
dpn[i].num_ch = nval;
if (dpn[i].num_ch) {
dpn[i].ch = devm_kcalloc(&slave->dev, nval,
sizeof(*dpn[i].ch), GFP_KERNEL);
if (!dpn[i].ch)
return -ENOMEM;
fwnode_property_read_u32_array(node,
"mipi-sdw-channel-number-list",
dpn[i].ch, nval);
}
nval = fwnode_property_read_u32_array(node,
"mipi-sdw-channel-combination-list", NULL, 0);
if (nval > 0)
dpn[i].num_ch_combinations = nval;
if (dpn[i].num_ch_combinations) {
dpn[i].ch_combinations = devm_kcalloc(&slave->dev,
nval, sizeof(*dpn[i].ch_combinations),
GFP_KERNEL);
if (!dpn[i].ch_combinations)
return -ENOMEM;
fwnode_property_read_u32_array(node,
"mipi-sdw-channel-combination-list",
dpn[i].ch_combinations, nval);
}
fwnode_property_read_u32(node,
"mipi-sdw-modes-supported", &dpn[i].modes);
fwnode_property_read_u32(node, "mipi-sdw-max-async-buffer",
&dpn[i].max_async_buffer);
dpn[i].block_pack_mode = fwnode_property_read_bool(node,
"mipi-sdw-block-packing-mode");
fwnode_property_read_u32(node, "mipi-sdw-port-encoding-type",
&dpn[i].port_encoding);
/* TODO: Read audio mode */
i++;
- }
- return 0;
+}
+/**
- sdw_slave_read_prop() - Read Slave properties
- @slave: SDW Slave
- */
+int sdw_slave_read_prop(struct sdw_slave *slave) +{
- struct sdw_slave_prop *prop = &slave->prop;
- struct device *dev = &slave->dev;
- struct fwnode_handle *port;
- int num_of_ports, nval, i;
- device_property_read_u32(dev, "mipi-sdw-sw-interface-revision",
&prop->mipi_revision);
- prop->wake_capable = device_property_read_bool(dev,
"mipi-sdw-wake-up-unavailable");
- prop->wake_capable = !prop->wake_capable;
- prop->test_mode_capable = device_property_read_bool(dev,
"mipi-sdw-test-mode-supported");
- prop->clk_stop_mode1 = false;
- if (device_property_read_bool(dev,
"mipi-sdw-clock-stop-mode1-supported"))
prop->clk_stop_mode1 = true;
- prop->simple_clk_stop_capable = device_property_read_bool(dev,
"mipi-sdw-simplified-clockstopprepare-sm-supported");
- device_property_read_u32(dev, "mipi-sdw-clockstopprepare-timeout",
&prop->clk_stop_timeout);
- device_property_read_u32(dev, "mipi-sdw-slave-channelprepare-timeout",
&prop->ch_prep_timeout);
- device_property_read_u32(dev,
"mipi-sdw-clockstopprepare-hard-reset-behavior",
&prop->reset_behave);
- prop->high_PHY_capable = device_property_read_bool(dev,
"mipi-sdw-highPHY-capable");
- prop->paging_support = device_property_read_bool(dev,
"mipi-sdw-paging-support");
- prop->bank_delay_support = device_property_read_bool(dev,
"mipi-sdw-bank-delay-support");
- device_property_read_u32(dev,
"mipi-sdw-port15-read-behavior", &prop->p15_behave);
- device_property_read_u32(dev, "mipi-sdw-master-count",
&prop->master_count);
- device_property_read_u32(dev, "mipi-sdw-source-port-list",
&prop->source_ports);
- device_property_read_u32(dev, "mipi-sdw-sink-port-list",
&prop->sink_ports);
- /* Read dp0 properties */
- port = device_get_named_child_node(dev, "mipi-sdw-dp-0-subproperties");
- if (!port) {
dev_err(dev, "DP0 node not found!!\n");
return -EIO;
- }
- prop->dp0_prop = devm_kzalloc(&slave->dev,
sizeof(*prop->dp0_prop), GFP_KERNEL);
- if (!prop->dp0_prop)
return -ENOMEM;
- fwnode_property_read_u32(port, "mipi-sdw-port-max-wordlength",
&prop->dp0_prop->max_word);
- fwnode_property_read_u32(port, "mipi-sdw-port-min-wordlength",
&prop->dp0_prop->min_word);
- nval = fwnode_property_read_u32_array(port,
"mipi-sdw-port-wordlength-configs", NULL, 0);
- if (nval > 0)
prop->dp0_prop->num_words = nval;
- if (prop->dp0_prop->num_words) {
prop->dp0_prop->words = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->dp0_prop->words), GFP_KERNEL);
if (!prop->dp0_prop->words)
return -ENOMEM;
fwnode_property_read_u32_array(port,
"mipi-sdw-port-wordlength-configs",
prop->dp0_prop->words, nval);
- }
- prop->dp0_prop->flow_controlled = fwnode_property_read_bool(port,
"mipi-sdw-bra-flow-controlled");
- prop->dp0_prop->simple_ch_prep_sm = fwnode_property_read_bool(port,
"mipi-sdw-simplified-channel-prepare-sm");
- prop->dp0_prop->device_interrupts = fwnode_property_read_bool(port,
"mipi-sdw-imp-def-dp0-interrupts-supported");
- /*
* Based on each DPn port, get source and sink dpn properties.
* Also, some ports can operate as both source or sink.
*/
- /* Allocate memory for set bits in port lists */
- nval = hweight32(prop->source_ports);
- num_of_ports += nval;
this and...
- prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->src_dpn_prop), GFP_KERNEL);
- if (!prop->src_dpn_prop)
return -ENOMEM;
- /* Read dpn properties for source port(s) */
- sdw_slave_read_dpn(slave, prop->src_dpn_prop, nval,
prop->source_ports, "source");
- nval = hweight32(prop->sink_ports);
- num_of_ports += nval;
... this is no longer needed since...
- prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->sink_dpn_prop), GFP_KERNEL);
- if (!prop->sink_dpn_prop)
return -ENOMEM;
- /* Read dpn properties for sink port(s) */
- sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval,
prop->sink_ports, "sink");
- /* some ports are bidirectional so check total ports by ORing */
- nval = prop->source_ports | prop->sink_ports;
- num_of_ports = hweight32(nval) + 1; /* add 1 for DP0 */
... you reassign the value here. That was one earlier feedback from me but you left the variable incrementation in the code.
- /* Allocate port_ready based on num_of_ports */
- slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports,
sizeof(*slave->port_ready), GFP_KERNEL);
- if (!slave->port_ready)
return -ENOMEM;
- /* Initialize completion */
- for (i = 0; i < num_of_ports; i++ > + init_completion(&slave->port_ready[i]);
- return 0;
+} +EXPORT_SYMBOL(sdw_slave_read_prop); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 7e9579941c66..ddfae18f4306 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -32,6 +32,252 @@ enum sdw_slave_status { };
/*
- SDW properties, defined in MIPI DisCo spec v1.0
- */
+enum sdw_clk_stop_reset_behave {
- SDW_CLK_STOP_KEEP_STATUS = 1,
+};
+/**
- enum sdw_p15_behave - Slave Port 15 behaviour when the Master attempts a
- read
- @SDW_P15_READ_IGNORED: Read is ignored
- @SDW_P15_CMD_OK: Command is ok
- */
+enum sdw_p15_behave {
- SDW_P15_READ_IGNORED = 0,
- SDW_P15_CMD_OK = 1,
+};
+/**
- enum sdw_dpn_type - Data port types
- @SDW_DPN_FULL: Full Data Port is supported
- @SDW_DPN_SIMPLE: Simplified Data Port as defined in spec.
- DPN_SampleCtrl2, DPN_OffsetCtrl2, DPN_HCtrl and DPN_BlockCtrl3
- are not implemented.
- @SDW_DPN_REDUCED: Reduced Data Port as defined in spec.
- DPN_SampleCtrl2, DPN_HCtrl are not implemented.
- */
+enum sdw_dpn_type {
- SDW_DPN_FULL = 0,
- SDW_DPN_SIMPLE = 1,
- SDW_DPN_REDUCED = 2,
+};
+/**
- enum sdw_clk_stop_mode - Clock Stop modes
- @SDW_CLK_STOP_MODE0: Slave can continue operation seamlessly on clock
- restart
- @SDW_CLK_STOP_MODE1: Slave may have entered a deeper power-saving mode,
- not capable of continuing operation seamlessly when the clock restarts
- */
+enum sdw_clk_stop_mode {
- SDW_CLK_STOP_MODE0 = 1,
- SDW_CLK_STOP_MODE1 = 2,
why not 0 and 1?
+};
+/**
- struct sdw_dp0_prop - DP0 properties
- @max_word: Maximum number of bits in a Payload Channel Sample, 1 to 64
- (inclusive)
- @min_word: Minimum number of bits in a Payload Channel Sample, 1 to 64
- (inclusive)
- @num_words: number of wordlengths supported
- @words: wordlengths supported
- @flow_controlled: Slave implementation results in an OK_NotReady
- response
- @simple_ch_prep_sm: If channel prepare sequence is required
- @device_interrupts: If implementation-defined interrupts are supported
- The wordlengths are specified by Spec as max, min AND number of
- discrete values, implementation can define based on the wordlengths they
- support
- */
+struct sdw_dp0_prop {
- u32 max_word;
- u32 min_word;
- u32 num_words;
- u32 *words;
- bool flow_controlled;
- bool simple_ch_prep_sm;
- bool device_interrupts;
+};
+/**
- struct sdw_dpn_audio_mode - Audio mode properties for DPn
- @bus_min_freq: Minimum bus frequency, in Hz
- @bus_max_freq: Maximum bus frequency, in Hz
- @bus_num_freq: Number of discrete frequencies supported
- @bus_freq: Discrete bus frequencies, in Hz
- @min_freq: Minimum sampling frequency, in Hz
- @max_freq: Maximum sampling bus frequency, in Hz
- @num_freq: Number of discrete sampling frequency supported
- @freq: Discrete sampling frequencies, in Hz
- @prep_ch_behave: Specifies the dependencies between Channel Prepare
- sequence and bus clock configuration
- If 0, Channel Prepare can happen at any Bus clock rate
- If 1, Channel Prepare sequence shall happen only after Bus clock is
- changed to a frequency supported by this mode or compatible modes
- described by the next field
- @glitchless: Bitmap describing possible glitchless transitions from this
- Audio Mode to other Audio Modes
- */
+struct sdw_dpn_audio_mode {
- u32 bus_min_freq;
- u32 bus_max_freq;
- u32 bus_num_freq;
- u32 *bus_freq;
- u32 max_freq;
- u32 min_freq;
- u32 num_freq;
- u32 *freq;
- u32 prep_ch_behave;
- u32 glitchless;
+};
+/**
- struct sdw_dpn_prop - Data Port DPn properties
- @num: port number
- @max_word: Maximum number of bits in a Payload Channel Sample, 1 to 64
- (inclusive)
- @min_word: Minimum number of bits in a Payload Channel Sample, 1 to 64
- (inclusive)
- @num_words: Number of discrete supported wordlengths
- @words: Discrete supported wordlength
- @type: Data port type. Full, Simplified or Reduced
- @max_grouping: Maximum number of samples that can be grouped together for
- a full data port
- @simple_ch_prep_sm: If the port supports simplified channel prepare state
- machine
- @ch_prep_timeout: Port-specific timeout value, in milliseconds
- @device_interrupts: If set, each bit corresponds to support for
- implementation-defined interrupts
- @max_ch: Maximum channels supported
- @min_ch: Minimum channels supported
- @num_ch: Number of discrete channels supported
- @ch: Discrete channels supported
- @num_ch_combinations: Number of channel combinations supported
- @ch_combinations: Channel combinations supported
- @modes: SDW mode supported
- @max_async_buffer: Number of samples that this port can buffer in
- asynchronous modes
- @block_pack_mode: Type of block port mode supported
- @port_encoding: Payload Channel Sample encoding schemes supported
- @audio_modes: Audio modes supported
- */
+struct sdw_dpn_prop {
- u32 num;
- u32 max_word;
- u32 min_word;
- u32 num_words;
- u32 *words;
- enum sdw_dpn_type type;
- u32 max_grouping;
- bool simple_ch_prep_sm;
- u32 ch_prep_timeout;
- u32 device_interrupts;
- u32 max_ch;
- u32 min_ch;
- u32 num_ch;
- u32 *ch;
- u32 num_ch_combinations;
- u32 *ch_combinations;
- u32 modes;
- u32 max_async_buffer;
- bool block_pack_mode;
- u32 port_encoding;
- struct sdw_dpn_audio_mode *audio_modes;
+};
+/**
- struct sdw_slave_prop - SoundWire Slave properties
- @mipi_revision: Spec version of the implementation
- @wake_capable: Wake-up events are supported
- @test_mode_capable: If test mode is supported
- @clk_stop_mode1: Clock-Stop Mode 1 is supported
- @simple_clk_stop_capable: Simple clock mode is supported
- @clk_stop_timeout: Worst-case latency of the Clock Stop Prepare State
- Machine transitions, in milliseconds
- @ch_prep_timeout: Worst-case latency of the Channel Prepare State Machine
- transitions, in milliseconds
- @reset_behave: Slave keeps the status of the SlaveStopClockPrepare
- state machine (P=1 SCSP_SM) after exit from clock-stop mode1
- @high_PHY_capable: Slave is HighPHY capable
- @paging_support: Slave implements paging registers SCP_AddrPage1 and
- SCP_AddrPage2
- @bank_delay_support: Slave implements bank delay/bridge support registers
- SCP_BankDelay and SCP_NextFrame
- @p15_behave: Slave behavior when the Master attempts a read to the Port15
- alias
- @lane_control_support: Slave supports lane control
- @master_count: Number of Masters present on this Slave
- @source_ports: Bitmap identifying source ports
- @sink_ports: Bitmap identifying sink ports
- @dp0_prop: Data Port 0 properties
- @src_dpn_prop: Source Data Port N properties
- @sink_dpn_prop: Sink Data Port N properties
- */
+struct sdw_slave_prop {
- u32 mipi_revision;
- bool wake_capable;
- bool test_mode_capable;
- bool clk_stop_mode1;
- bool simple_clk_stop_capable;
- u32 clk_stop_timeout;
- u32 ch_prep_timeout;
- enum sdw_clk_stop_reset_behave reset_behave;
- bool high_PHY_capable;
- bool paging_support;
- bool bank_delay_support;
- enum sdw_p15_behave p15_behave;
- bool lane_control_support;
- u32 master_count;
- u32 source_ports;
- u32 sink_ports;
- struct sdw_dp0_prop *dp0_prop;
- struct sdw_dpn_prop *src_dpn_prop;
- struct sdw_dpn_prop *sink_dpn_prop;
+};
+/**
- struct sdw_master_prop - Master properties
- @revision: MIPI spec version of the implementation
- @master_count: Number of masters
- @clk_stop_mode: Bitmap for Clock Stop modes supported
- @max_freq: Maximum Bus clock frequency, in Hz
- @num_clk_gears: Number of clock gears supported
- @clk_gears: Clock gears supported
- @num_freq: Number of clock frequencies supported, in Hz
- @freq: Clock frequencies supported, in Hz
- @default_frame_rate: Controller default Frame rate, in Hz
- @default_row: Number of rows
- @default_col: Number of columns
- @dynamic_frame: Dynamic frame supported
- @err_threshold: Number of times that software may retry sending a single
- command
- @dpn_prop: Data Port N properties
- */
+struct sdw_master_prop {
- u32 revision;
- u32 master_count;
- enum sdw_clk_stop_mode clk_stop_mode;
- u32 max_freq;
- u32 num_clk_gears;
- u32 *clk_gears;
- u32 num_freq;
- u32 *freq;
- u32 default_frame_rate;
- u32 default_row;
- u32 default_col;
- bool dynamic_frame;
- u32 err_threshold;
- struct sdw_dpn_prop *dpn_prop;
+};
+int sdw_master_read_prop(struct sdw_bus *bus); +int sdw_slave_read_prop(struct sdw_slave *slave);
+/*
- SDW Slave Structures and APIs
*/
@@ -55,12 +301,23 @@ struct sdw_slave_id { };
/**
- struct sdw_slave_ops - Slave driver callback ops
- @read_prop: Read Slave properties
- */
+struct sdw_slave_ops {
- int (*read_prop)(struct sdw_slave *sdw);
+};
+/**
- struct sdw_slave - SoundWire Slave
- @id: MIPI device ID
- @dev: Linux device
- @status: Status reported by the Slave
- @bus: Bus handle
- @ops: Slave callback ops
- @prop: Slave properties
- @node: node for bus list
*/ struct sdw_slave {
- @port_ready: Port ready completion flag for each Slave port
- @dev_num: Device Number assigned by Bus
@@ -68,7 +325,10 @@ struct sdw_slave { struct device dev; enum sdw_slave_status status; struct sdw_bus *bus;
- const struct sdw_slave_ops *ops;
- struct sdw_slave_prop prop; struct list_head node;
- struct completion *port_ready; u16 dev_num; };
@@ -100,6 +360,14 @@ int sdw_handle_slave_status(struct sdw_bus *bus, */
/**
- struct sdw_master_ops - Master driver ops
- @read_prop: Read Master properties
- */
+struct sdw_master_ops {
- int (*read_prop)(struct sdw_bus *bus);
+};
+/**
- struct sdw_bus - SoundWire bus
- @dev: Master linux device
- @link_id: Link id number, can be 0 to N, unique for each Master
@@ -107,6 +375,9 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
- @assigned: Bitmap for Slave device numbers.
- Bit set implies used number, bit clear implies unused number.
- @bus_lock: bus lock
- @ops: Master callback ops
- @prop: Master properties
*/ struct sdw_bus { struct device *dev;
- @clk_stop_timeout: Clock stop timeout computed
@@ -114,6 +385,9 @@ struct sdw_bus { struct list_head slaves; DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); struct mutex bus_lock;
const struct sdw_master_ops *ops;
struct sdw_master_prop prop;
unsigned int clk_stop_timeout; };
int sdw_add_bus_master(struct sdw_bus *bus);
On Fri, Dec 01, 2017 at 04:49:01PM -0600, Pierre-Louis Bossart wrote:
+int sdw_master_read_prop(struct sdw_bus *bus) +{
- struct sdw_master_prop *prop = &bus->prop;
- struct fwnode_handle *link;
- unsigned int count = 0;
- char name[32];
- int nval, i;
- device_property_read_u32(bus->dev,
"mipi-sdw-sw-interface-revision", &prop->revision);
- device_property_read_u32(bus->dev, "mipi-sdw-master-count", &count);
- /* Find link handle */
- snprintf(name, sizeof(name),
"mipi-sdw-link-%d-subproperties", bus->link_id);
if you follow the DisCo spec, this property is at the controller level, isn't there a confusion between controller/master here, and consequently are we reading the same things multiple times or using the wrong bus parameter?
Not sure I follow, this one is for a specific master ie a specfic link. we need to read respective master thru mipi-sdw-link-N-subproperties
If I look at intel_probe(), there is a clear reference to a link_id, and then you set the pointer to this read_prop which reads the number of links, which looks like the wrong order. You can't assign a link ID before knowing how many links there are - or you may be unable to detect issues.
Sorry I dont follow this part. FWIW, when master driver is enumerated it know the link_id value and then sets the read_prop and then these are read.
Here we are reading "a specific link property" with the knowledge of link_id value...
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate",
&prop->default_frame_rate);
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size",
&prop->default_row);
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size",
This is fine, just wondering if we should warnings if the values make no sense, e.g. the DisCo spec states in Note1 page 15 that the values are interrelated.
I think we discussed in past and that would kind of form the firmware validation. We check all the values to see if firmware gave us sane values..
- /*
* Based on each DPn port, get source and sink dpn properties.
* Also, some ports can operate as both source or sink.
*/
- /* Allocate memory for set bits in port lists */
- nval = hweight32(prop->source_ports);
- num_of_ports += nval;
this and...
- prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->src_dpn_prop), GFP_KERNEL);
- if (!prop->src_dpn_prop)
return -ENOMEM;
- /* Read dpn properties for source port(s) */
- sdw_slave_read_dpn(slave, prop->src_dpn_prop, nval,
prop->source_ports, "source");
- nval = hweight32(prop->sink_ports);
- num_of_ports += nval;
... this is no longer needed since...
- prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->sink_dpn_prop), GFP_KERNEL);
- if (!prop->sink_dpn_prop)
return -ENOMEM;
- /* Read dpn properties for sink port(s) */
- sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval,
prop->sink_ports, "sink");
- /* some ports are bidirectional so check total ports by ORing */
- nval = prop->source_ports | prop->sink_ports;
- num_of_ports = hweight32(nval) + 1; /* add 1 for DP0 */
... you reassign the value here. That was one earlier feedback from me but you left the variable incrementation in the code.
This seems to have artifact of merge conflicts as I clearly remember removing this, thanks for pointing will remove these..
+/**
- enum sdw_clk_stop_mode - Clock Stop modes
- @SDW_CLK_STOP_MODE0: Slave can continue operation seamlessly on clock
- restart
- @SDW_CLK_STOP_MODE1: Slave may have entered a deeper power-saving mode,
- not capable of continuing operation seamlessly when the clock restarts
- */
+enum sdw_clk_stop_mode {
- SDW_CLK_STOP_MODE0 = 1,
- SDW_CLK_STOP_MODE1 = 2,
why not 0 and 1?
why not 1 and 2 :D
I think it was to ensure we have a non zero value, but am not sure, will check though..
On 12/3/17 10:52 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 04:49:01PM -0600, Pierre-Louis Bossart wrote:
+int sdw_master_read_prop(struct sdw_bus *bus) +{
- struct sdw_master_prop *prop = &bus->prop;
- struct fwnode_handle *link;
- unsigned int count = 0;
- char name[32];
- int nval, i;
- device_property_read_u32(bus->dev,
"mipi-sdw-sw-interface-revision", &prop->revision);
- device_property_read_u32(bus->dev, "mipi-sdw-master-count", &count);
- /* Find link handle */
- snprintf(name, sizeof(name),
"mipi-sdw-link-%d-subproperties", bus->link_id);
if you follow the DisCo spec, this property is at the controller level, isn't there a confusion between controller/master here, and consequently are we reading the same things multiple times or using the wrong bus parameter?
Not sure I follow, this one is for a specific master ie a specfic link. we need to read respective master thru mipi-sdw-link-N-subproperties
If I look at intel_probe(), there is a clear reference to a link_id, and then you set the pointer to this read_prop which reads the number of links, which looks like the wrong order. You can't assign a link ID before knowing how many links there are - or you may be unable to detect issues.
Sorry I dont follow this part. FWIW, when master driver is enumerated it know the link_id value and then sets the read_prop and then these are read.
Here we are reading "a specific link property" with the knowledge of link_id value...
the sdw_master-count is at the controller level, and the linkid has to be < master_count.
The fact that you are reading this property for each master instance is the problem.
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate",
&prop->default_frame_rate);
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size",
&prop->default_row);
- fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size",
This is fine, just wondering if we should warnings if the values make no sense, e.g. the DisCo spec states in Note1 page 15 that the values are interrelated.
I think we discussed in past and that would kind of form the firmware validation. We check all the values to see if firmware gave us sane values..
- /*
* Based on each DPn port, get source and sink dpn properties.
* Also, some ports can operate as both source or sink.
*/
- /* Allocate memory for set bits in port lists */
- nval = hweight32(prop->source_ports);
- num_of_ports += nval;
this and...
- prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->src_dpn_prop), GFP_KERNEL);
- if (!prop->src_dpn_prop)
return -ENOMEM;
- /* Read dpn properties for source port(s) */
- sdw_slave_read_dpn(slave, prop->src_dpn_prop, nval,
prop->source_ports, "source");
- nval = hweight32(prop->sink_ports);
- num_of_ports += nval;
... this is no longer needed since...
- prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->sink_dpn_prop), GFP_KERNEL);
- if (!prop->sink_dpn_prop)
return -ENOMEM;
- /* Read dpn properties for sink port(s) */
- sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval,
prop->sink_ports, "sink");
- /* some ports are bidirectional so check total ports by ORing */
- nval = prop->source_ports | prop->sink_ports;
- num_of_ports = hweight32(nval) + 1; /* add 1 for DP0 */
... you reassign the value here. That was one earlier feedback from me but you left the variable incrementation in the code.
This seems to have artifact of merge conflicts as I clearly remember removing this, thanks for pointing will remove these..
+/**
- enum sdw_clk_stop_mode - Clock Stop modes
- @SDW_CLK_STOP_MODE0: Slave can continue operation seamlessly on clock
- restart
- @SDW_CLK_STOP_MODE1: Slave may have entered a deeper power-saving mode,
- not capable of continuing operation seamlessly when the clock restarts
- */
+enum sdw_clk_stop_mode {
- SDW_CLK_STOP_MODE0 = 1,
- SDW_CLK_STOP_MODE1 = 2,
why not 0 and 1?
why not 1 and 2 :D
I think it was to ensure we have a non zero value, but am not sure, will check though..
I don't think the value matter and you should use the same conventions for such enums.
From: Sanyog Kale sanyog.r.kale@intel.com
MIPI SoundWire spec defines standard SoundWire registers mandatory for SoundWire Slave devices, so add them.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/linux/soundwire/sdw_registers.h | 194 ++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 include/linux/soundwire/sdw_registers.h
diff --git a/include/linux/soundwire/sdw_registers.h b/include/linux/soundwire/sdw_registers.h new file mode 100644 index 000000000000..df472b1ab410 --- /dev/null +++ b/include/linux/soundwire/sdw_registers.h @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_REGISTERS_H +#define __SDW_REGISTERS_H + +/* + * typically we define register and shifts but if one observes carefully, + * the shift can be generated from MASKS using few bit primitaives like ffs + * etc, so we use that and avoid defining shifts + */ +#define SDW_REG_SHIFT(n) (ffs(n) - 1) + +/* + * SDW registers as defined by MIPI 1.1 Spec + */ +#define SDW_REGADDR GENMASK(14, 0) +#define SDW_SCP_ADDRPAGE2_MASK GENMASK(22, 15) +#define SDW_SCP_ADDRPAGE1_MASK GENMASK(30, 23) + +#define SDW_REG_NO_PAGE 0x00008000 +#define SDW_REG_OPTIONAL_PAGE 0x00010000 +#define SDW_REG_MAX 0x80000000 + +#define SDW_DPN_SIZE 0x100 +#define SDW_BANK1_OFFSET 0x10 + +/* + * DP0 Interrupt register & bits + * + * Spec treats Status (RO) and Clear (WC) as separate but they are same + * address, so treat as same register with WC. + */ + +/* both INT and STATUS register are same */ +#define SDW_DP0_INT 0x0 +#define SDW_DP0_INTMASK 0x1 +#define SDW_DP0_PORTCTRL 0x2 +#define SDW_DP0_BLOCKCTRL1 0x3 +#define SDW_DP0_PREPARESTATUS 0x4 +#define SDW_DP0_PREPARECTRL 0x5 + +#define SDW_DP0_INT_TEST_FAIL BIT(0) +#define SDW_DP0_INT_PORT_READY BIT(1) +#define SDW_DP0_INT_BRA_FAILURE BIT(2) +#define SDW_DP0_INT_IMPDEF1 BIT(5) +#define SDW_DP0_INT_IMPDEF2 BIT(6) +#define SDW_DP0_INT_IMPDEF3 BIT(7) + +#define SDW_DP0_PORTCTRL_DATAMODE GENMASK(3, 2) +#define SDW_DP0_PORTCTRL_NXTINVBANK BIT(4) +#define SDW_DP0_PORTCTRL_BPT_PAYLD GENMASK(7, 6) + +#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 + +/* Both INT and STATUS register are same */ +#define SDW_SCP_INT1 0x40 +#define SDW_SCP_INTMASK1 0x41 + +#define SDW_SCP_INT1_PARITY BIT(0) +#define SDW_SCP_INT1_BUS_CLASH BIT(1) +#define SDW_SCP_INT1_IMPL_DEF BIT(2) +#define SDW_SCP_INT1_SCP2_CASCADE BIT(7) +#define SDW_SCP_INT1_PORT0_3 GENMASK(6, 3) + +#define SDW_SCP_INTSTAT2 0x42 +#define SDW_SCP_INTSTAT2_SCP3_CASCADE BIT(7) +#define SDW_SCP_INTSTAT2_PORT4_10 GENMASK(6, 0) + + +#define SDW_SCP_INTSTAT3 0x43 +#define SDW_SCP_INTSTAT3_PORT11_14 GENMASK(3, 0) + +/* 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 BIT(1) +#define SDW_SCP_CTRL_FORCE_RESET BIT(7) + +#define SDW_SCP_STAT 0x44 +#define SDW_SCP_STAT_CLK_STP_NF BIT(0) +#define SDW_SCP_STAT_HPHY_NOK BIT(5) +#define SDW_SCP_STAT_CURR_BANK BIT(6) + +#define SDW_SCP_SYSTEMCTRL 0x45 +#define SDW_SCP_SYSTEMCTRL_CLK_STP_PREP BIT(0) +#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE BIT(2) +#define SDW_SCP_SYSTEMCTRL_WAKE_UP_EN BIT(3) +#define SDW_SCP_SYSTEMCTRL_HIGH_PHY BIT(4) + +#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE0 0 +#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE1 BIT(2) + +#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_B0 0x60 +#define SDW_SCP_FRAMECTRL_B1 (0x60 + SDW_BANK1_OFFSET) +#define SDW_SCP_NEXTFRAME_B0 0x61 +#define SDW_SCP_NEXTFRAME_B1 (0x61 + SDW_BANK1_OFFSET) + +/* Both INT and STATUS register is same */ +#define SDW_DPN_INT(n) (0x0 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_INTMASK(n) (0x1 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_PORTCTRL(n) (0x2 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_BLOCKCTRL1(n) (0x3 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_PREPARESTATUS(n) (0x4 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_PREPARECTRL(n) (0x5 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_INT_TEST_FAIL BIT(0) +#define SDW_DPN_INT_PORT_READY BIT(1) +#define SDW_DPN_INT_IMPDEF1 BIT(5) +#define SDW_DPN_INT_IMPDEF2 BIT(6) +#define SDW_DPN_INT_IMPDEF3 BIT(7) + +#define SDW_DPN_PORTCTRL_FLOWMODE GENMASK(1, 0) +#define SDW_DPN_PORTCTRL_DATAMODE GENMASK(3, 2) +#define SDW_DPN_PORTCTRL_NXTINVBANK BIT(4) + +#define SDW_DPN_BLOCKCTRL1_WDLEN GENMASK(5, 0) + +#define SDW_DPN_PREPARECTRL_CH_PREP GENMASK(7, 0) + +#define SDW_DPN_CHANNELEN_B0(n) (0x20 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_CHANNELEN_B1(n) (0x30 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_BLOCKCTRL2_B0(n) (0x21 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_BLOCKCTRL2_B1(n) (0x31 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_SAMPLECTRL1_B0(n) (0x22 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_SAMPLECTRL1_B1(n) (0x32 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_SAMPLECTRL2_B0(n) (0x23 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_SAMPLECTRL2_B1(n) (0x33 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_OFFSETCTRL1_B0(n) (0x24 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_OFFSETCTRL1_B1(n) (0x34 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_OFFSETCTRL2_B0(n) (0x25 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_OFFSETCTRL2_B1(n) (0x35 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_HCTRL_B0(n) (0x26 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_HCTRL_B1(n) (0x36 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_BLOCKCTRL3_B0(n) (0x27 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_BLOCKCTRL3_B1(n) (0x37 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_LANECTRL_B0(n) (0x28 + SDW_DPN_SIZE * (n)) +#define SDW_DPN_LANECTRL_B1(n) (0x38 + SDW_DPN_SIZE * (n)) + +#define SDW_DPN_SAMPLECTRL_LOW GENMASK(7, 0) +#define SDW_DPN_SAMPLECTRL_HIGH GENMASK(15, 8) + +#define SDW_DPN_HCTRL_HSTART GENMASK(7, 4) +#define SDW_DPN_HCTRL_HSTOP GENMASK(3, 0) + +#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 /* __SDW_REGISTERS_H */
SoundWire bus supports read or write register(s) for SoundWire Slave device. sdw_read() and sdw_write() APIs are provided for single register read/write. sdw_nread() and sdw_nwrite() for operations on contiguous registers.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/bus.c | 275 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 37 ++++++ include/linux/soundwire/sdw.h | 57 +++++++++ 3 files changed, 369 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 507ae85ad58e..2f108f162905 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -3,6 +3,8 @@
#include <linux/acpi.h> #include <linux/mod_devicetable.h> +#include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw.h> #include "bus.h"
@@ -22,6 +24,12 @@ int sdw_add_bus_master(struct sdw_bus *bus) return -ENODEV; }
+ if (!bus->ops) { + dev_err(bus->dev, "SoundWire Bus ops are not set"); + return -EINVAL; + } + + mutex_init(&bus->msg_lock); mutex_init(&bus->bus_lock); INIT_LIST_HEAD(&bus->slaves);
@@ -97,6 +105,273 @@ void sdw_delete_bus_master(struct sdw_bus *bus) } EXPORT_SYMBOL(sdw_delete_bus_master);
+/* + * SDW IO Calls + */ + +static inline int find_response_code(enum sdw_command_response resp) +{ + switch (resp) { + case SDW_CMD_OK: + return 0; + + case SDW_CMD_IGNORED: + return -ENODATA; + + case SDW_CMD_TIMEOUT: + return -ETIMEDOUT; + + default: + return -EIO; + } +} + +static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + for (i = 0; i <= retry; i++) { + resp = bus->ops->xfer_msg(bus, msg); + ret = find_response_code(resp); + + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +static inline int do_transfer_defer(struct sdw_bus *bus, + struct sdw_msg *msg, struct sdw_defer *defer) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + defer->msg = msg; + defer->length = msg->len; + + for (i = 0; i <= retry; i++) { + resp = bus->ops->xfer_msg_defer(bus, msg, defer); + ret = find_response_code(resp); + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +static int sdw_reset_page(struct sdw_bus *bus, u16 dev_num) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + for (i = 0; i <= retry; i++) { + resp = bus->ops->reset_page_addr(bus, dev_num); + ret = find_response_code(resp); + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +/** + * sdw_transfer() - Synchronous transfer message to a SDW Slave device + * @bus: SDW bus + * @slave: SDW Slave + * @msg: SDW message to be xfered + */ +int sdw_transfer(struct sdw_bus *bus, + struct sdw_slave *slave, struct sdw_msg *msg) +{ + int ret; + + mutex_lock(&bus->msg_lock); + + ret = do_transfer(bus, msg); + if (ret != 0 && ret != -ENODATA) + dev_err(bus->dev, "trf on Slave %d failed:%d\n", + msg->dev_num, ret); + + if (msg->page) + sdw_reset_page(bus, msg->dev_num); + + mutex_unlock(&bus->msg_lock); + + return ret; +} + +/** + * sdw_transfer_defer() - Asynchronously transfer message to a SDW Slave device + * @bus: SDW bus + * @slave: SDW Slave + * @msg: SDW message to be xfered + * @defer: Defer block for signal completion + * + * Caller needs to hold the msg_lock lock while calling this + */ +int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_slave *slave, + struct sdw_msg *msg, struct sdw_defer *defer) +{ + int ret; + + if (!bus->ops->xfer_msg_defer) + return -ENOTSUPP; + + ret = do_transfer_defer(bus, msg, defer); + if (ret != 0 && ret != -ENODATA) + dev_err(bus->dev, "Defer trf on Slave %d failed:%d\n", + msg->dev_num, ret); + + if (msg->page) + sdw_reset_page(bus, msg->dev_num); + + return ret; +} + + +int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, + u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf) +{ + memset(msg, 0, sizeof(*msg)); + msg->addr = addr; + msg->len = count; + msg->dev_num = dev_num; + msg->flags = flags; + msg->buf = buf; + msg->ssp_sync = false; + msg->page = false; + + if (addr < SDW_REG_NO_PAGE) { /* no paging area */ + return 0; + } else if (addr >= SDW_REG_MAX) { /* illegal addr */ + pr_err("SDW: Invalid address %x passed\n", addr); + return -EINVAL; + } + + if (addr < SDW_REG_OPTIONAL_PAGE) { /* 32k but no page */ + if (slave && !slave->prop.paging_support) + return 0; + /* no need for else as that will fall thru to paging */ + } + + /* paging madatory */ + if (dev_num == SDW_ENUM_DEV_NUM || dev_num == SDW_BROADCAST_DEV_NUM) { + pr_err("SDW: Invalid device for paging :%d\n", dev_num); + return -EINVAL; + } + + if (!slave) { + pr_err("SDW: No slave for paging addr\n"); + return -EINVAL; + } else if (!slave->prop.paging_support) { + dev_err(&slave->dev, + "address %x needs paging but no support", addr); + return -EINVAL; + } + + msg->addr_page1 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE1_MASK)); + msg->addr_page2 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE2_MASK)); + msg->addr |= BIT(15); + msg->page = true; + + return 0; +} + +/** + * sdw_nread() - Read "n" contiguous SDW Slave registers + * @slave: SDW Slave + * @addr: Register address + * @count: length + * @val: Buffer for values to be read + */ +int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_READ, val); + if (ret < 0) + return ret; + + ret = pm_runtime_get_sync(slave->bus->dev); + if (!ret) + return ret; + + ret = sdw_transfer(slave->bus, slave, &msg); + pm_runtime_put(slave->bus->dev); + + return ret; +} +EXPORT_SYMBOL(sdw_nread); + +/** + * sdw_nwrite() - Write "n" contiguous SDW Slave registers + * @slave: SDW Slave + * @addr: Register address + * @count: length + * @val: Buffer for values to be read + */ +int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_WRITE, val); + if (ret < 0) + return ret; + + ret = pm_runtime_get_sync(slave->bus->dev); + if (!ret) + return ret; + + ret = sdw_transfer(slave->bus, slave, &msg); + pm_runtime_put(slave->bus->dev); + + return ret; +} +EXPORT_SYMBOL(sdw_nwrite); + +/** + * sdw_read() - Read a SDW Slave register + * @slave: SDW Slave + * @addr: Register address + */ +int sdw_read(struct sdw_slave *slave, u32 addr) +{ + u8 buf; + int ret; + + ret = sdw_nread(slave, addr, 1, &buf); + if (ret < 0) + return ret; + else + return buf; +} +EXPORT_SYMBOL(sdw_read); + +/** + * sdw_write() - Write a SDW Slave register + * @slave: SDW Slave + * @addr: Register address + * @value: Register value + */ +int sdw_write(struct sdw_slave *slave, u32 addr, u8 value) +{ + return sdw_nwrite(slave, addr, 1, &value); + +} +EXPORT_SYMBOL(sdw_write); + void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id) { diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index a54921825ce0..bde5f840ca96 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -16,4 +16,41 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id);
+enum { + SDW_MSG_FLAG_READ = 0, + SDW_MSG_FLAG_WRITE, +}; + +/** + * struct sdw_msg - Message structure + * @addr: Register address accessed in the Slave + * @len: number of messages + * @dev_num: Slave device number + * @addr_page1: SCP address page 1 Slave register + * @addr_page2: SCP address page 2 Slave register + * @flags: transfer flags, indicate if xfer is read or write + * @buf: message data buffer + * @ssp_sync: Send message at SSP (Stream Synchronization Point) + * @page: address requires paging + */ +struct sdw_msg { + u16 addr; + u16 len; + u16 dev_num; + u8 addr_page1; + u8 addr_page2; + u8 flags; + u8 *buf; + bool ssp_sync; + bool page; +}; + +int sdw_transfer(struct sdw_bus *bus, struct sdw_slave *slave, + struct sdw_msg *msg); +int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_slave *slave, + struct sdw_msg *msg, struct sdw_defer *defer); + +int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, + u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf); + #endif /* __SDW_BUS_H */ diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index ddfae18f4306..d3337b5b882b 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -31,6 +31,27 @@ enum sdw_slave_status { SDW_SLAVE_RESERVED = 3, };
+/** + * enum sdw_command_response - Command response as defined by SDW spec + * @SDW_CMD_OK: cmd was successful + * @SDW_CMD_IGNORED: cmd was ignored + * @SDW_CMD_FAIL: cmd was NACKed + * @SDW_CMD_TIMEOUT: cmd timedout + * @SDW_CMD_FAIL_OTHER: cmd failed due to other reason than above + * + * NOTE: The enum is different than actual Spec as response in the Spec is + * combination of ACK/NAK bits + * + * SDW_CMD_TIMEOUT/FAIL_OTHER is defined for SW use, not in spec + */ +enum sdw_command_response { + SDW_CMD_OK = 0, + SDW_CMD_IGNORED = 1, + SDW_CMD_FAIL = 2, + SDW_CMD_TIMEOUT = 4, + SDW_CMD_FAIL_OTHER = 8, +}; + /* * SDW properties, defined in MIPI DisCo spec v1.0 */ @@ -359,12 +380,37 @@ int sdw_handle_slave_status(struct sdw_bus *bus, * SDW master structures and APIs */
+struct sdw_msg; + +/** + * struct sdw_defer - SDW deffered message + * @length: message length + * @complete: message completion + * @msg: SDW message + */ +struct sdw_defer { + int length; + struct completion complete; + struct sdw_msg *msg; +}; + /** * struct sdw_master_ops - Master driver ops * @read_prop: Read Master properties + * @xfer_msg: Transfer message callback + * @xfer_msg_defer: Defer version of transfer message callback + * @reset_page_addr: Reset the SCP page address registers */ struct sdw_master_ops { int (*read_prop)(struct sdw_bus *bus); + + enum sdw_command_response (*xfer_msg) + (struct sdw_bus *bus, struct sdw_msg *msg); + enum sdw_command_response (*xfer_msg_defer) + (struct sdw_bus *bus, struct sdw_msg *msg, + struct sdw_defer *defer); + enum sdw_command_response (*reset_page_addr) + (struct sdw_bus *bus, unsigned int dev_num); };
/** @@ -375,8 +421,10 @@ struct sdw_master_ops { * @assigned: Bitmap for Slave device numbers. * Bit set implies used number, bit clear implies unused number. * @bus_lock: bus lock + * @msg_lock: message lock * @ops: Master callback ops * @prop: Master properties + * @defer_msg: Defer message * @clk_stop_timeout: Clock stop timeout computed */ struct sdw_bus { @@ -385,12 +433,21 @@ struct sdw_bus { struct list_head slaves; DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); struct mutex bus_lock; + struct mutex msg_lock; const struct sdw_master_ops *ops; struct sdw_master_prop prop; + struct sdw_defer defer_msg; unsigned int clk_stop_timeout; };
int sdw_add_bus_master(struct sdw_bus *bus); void sdw_delete_bus_master(struct sdw_bus *bus);
+/* messaging and data APIs */ + +int sdw_read(struct sdw_slave *slave, u32 addr); +int sdw_write(struct sdw_slave *slave, u32 addr, u8 value); +int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val); +int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val); + #endif /* __SOUNDWIRE_H */
On 12/1/17 3:56 AM, Vinod Koul wrote:
SoundWire bus supports read or write register(s) for SoundWire Slave device. sdw_read() and sdw_write() APIs are provided for single register read/write. sdw_nread() and sdw_nwrite() for operations on contiguous registers.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/bus.c | 275 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 37 ++++++ include/linux/soundwire/sdw.h | 57 +++++++++ 3 files changed, 369 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 507ae85ad58e..2f108f162905 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -3,6 +3,8 @@
#include <linux/acpi.h> #include <linux/mod_devicetable.h> +#include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw.h> #include "bus.h"
@@ -22,6 +24,12 @@ int sdw_add_bus_master(struct sdw_bus *bus) return -ENODEV; }
- if (!bus->ops) {
dev_err(bus->dev, "SoundWire Bus ops are not set");
return -EINVAL;
- }
- mutex_init(&bus->msg_lock); mutex_init(&bus->bus_lock); INIT_LIST_HEAD(&bus->slaves);
@@ -97,6 +105,273 @@ void sdw_delete_bus_master(struct sdw_bus *bus) } EXPORT_SYMBOL(sdw_delete_bus_master);
+/*
- SDW IO Calls
- */
+static inline int find_response_code(enum sdw_command_response resp) +{
- switch (resp) {
- case SDW_CMD_OK:
return 0;
- case SDW_CMD_IGNORED:
return -ENODATA;
- case SDW_CMD_TIMEOUT:
return -ETIMEDOUT;
- default:
return -EIO;
the 'default' case will handle both SDW_CMD_FAIL (which is a bus event usually due to bus clash or parity issues) and SDW_CMD_FAIL_OTHER (which is an imp-def IP event).
Do they really belong in the same basket? From a debug perspective there is quite a bit of information lost.
- }
+}
+static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{
- int retry = bus->prop.err_threshold;
- enum sdw_command_response resp;
- int ret = 0, i;
- for (i = 0; i <= retry; i++) {
resp = bus->ops->xfer_msg(bus, msg);
ret = find_response_code(resp);
/* if cmd is ok or ignored return */
if (ret == 0 || ret == -ENODATA)
Can you document why you don't retry on a CMD_IGNORED? I know there was a reason, I just can't remember it.
Now that I think of it, the retry on TIMEOUT makes no sense to me. The retry was intended for bus-level issues, where maybe a single bit error causes an issue without consequences, but the TIMEOUT is a completely different beast, it's the master IP that doesn't answer really, a completely different case.
return ret;
- }
- return ret;
+}
+static inline int do_transfer_defer(struct sdw_bus *bus,
struct sdw_msg *msg, struct sdw_defer *defer)
+{
- int retry = bus->prop.err_threshold;
- enum sdw_command_response resp;
- int ret = 0, i;
- defer->msg = msg;
- defer->length = msg->len;
- for (i = 0; i <= retry; i++) {
resp = bus->ops->xfer_msg_defer(bus, msg, defer);
ret = find_response_code(resp);
/* if cmd is ok or ignored return */
if (ret == 0 || ret == -ENODATA)
return ret;
- }
- return ret;
+}
+static int sdw_reset_page(struct sdw_bus *bus, u16 dev_num) +{
- int retry = bus->prop.err_threshold;
- enum sdw_command_response resp;
- int ret = 0, i;
- for (i = 0; i <= retry; i++) {
resp = bus->ops->reset_page_addr(bus, dev_num);
ret = find_response_code(resp);
/* if cmd is ok or ignored return */
if (ret == 0 || ret == -ENODATA)
return ret;
- }
- return ret;
+}
+/**
- sdw_transfer() - Synchronous transfer message to a SDW Slave device
- @bus: SDW bus
- @slave: SDW Slave
is this just me or this argument is not used?
- @msg: SDW message to be xfered
- */
+int sdw_transfer(struct sdw_bus *bus,
struct sdw_slave *slave, struct sdw_msg *msg)
+{
- int ret;
- mutex_lock(&bus->msg_lock);
- ret = do_transfer(bus, msg);
- if (ret != 0 && ret != -ENODATA)
dev_err(bus->dev, "trf on Slave %d failed:%d\n",
msg->dev_num, ret);
- if (msg->page)
sdw_reset_page(bus, msg->dev_num);
- mutex_unlock(&bus->msg_lock);
- return ret;
+}
+/**
- sdw_transfer_defer() - Asynchronously transfer message to a SDW Slave device
- @bus: SDW bus
- @slave: SDW Slave
same, this argument is not used in the code below.
- @msg: SDW message to be xfered
- @defer: Defer block for signal completion
- Caller needs to hold the msg_lock lock while calling this
- */
+int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_slave *slave,
struct sdw_msg *msg, struct sdw_defer *defer)
+{
- int ret;
- if (!bus->ops->xfer_msg_defer)
return -ENOTSUPP;
- ret = do_transfer_defer(bus, msg, defer);
- if (ret != 0 && ret != -ENODATA)
dev_err(bus->dev, "Defer trf on Slave %d failed:%d\n",
msg->dev_num, ret);
- if (msg->page)
sdw_reset_page(bus, msg->dev_num);
- return ret;
+}
+int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf)
+{
- memset(msg, 0, sizeof(*msg));
- msg->addr = addr;
add comment on implicit truncation to 16-bit address
- msg->len = count;
- msg->dev_num = dev_num;
- msg->flags = flags;
- msg->buf = buf;
- msg->ssp_sync = false;
- msg->page = false;
- if (addr < SDW_REG_NO_PAGE) { /* no paging area */
return 0;
- } else if (addr >= SDW_REG_MAX) { /* illegal addr */
pr_err("SDW: Invalid address %x passed\n", addr);
return -EINVAL;
- }
- if (addr < SDW_REG_OPTIONAL_PAGE) { /* 32k but no page */
if (slave && !slave->prop.paging_support)
return 0;
/* no need for else as that will fall thru to paging */
- }
- /* paging madatory */
mandatory
- if (dev_num == SDW_ENUM_DEV_NUM || dev_num == SDW_BROADCAST_DEV_NUM) {
pr_err("SDW: Invalid device for paging :%d\n", dev_num);
return -EINVAL;
- }
- if (!slave) {
pr_err("SDW: No slave for paging addr\n");
return -EINVAL;
I would move this test up, since if you have a NULL slave you should return an error in all case, otherwise there will be an oops in the code below ...
- } else if (!slave->prop.paging_support) {
dev_err(&slave->dev,
"address %x needs paging but no support", addr);
return -EINVAL;
- }
- msg->addr_page1 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE1_MASK));
- msg->addr_page2 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE2_MASK));
- msg->addr |= BIT(15);
- msg->page = true;
looks ok :-)
- return 0;
+}
+/**
- sdw_nread() - Read "n" contiguous SDW Slave registers
- @slave: SDW Slave
- @addr: Register address
- @count: length
- @val: Buffer for values to be read
- */
+int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{
- struct sdw_msg msg;
- int ret;
- ret = sdw_fill_msg(&msg, slave, addr, count,
slave->dev_num, SDW_MSG_FLAG_READ, val);
- if (ret < 0)
return ret;
... if you don't test for the slave argument in the sdw_fill_msg but the address is correct then the rest of the code will bomb out.
- ret = pm_runtime_get_sync(slave->bus->dev);
- if (!ret)
return ret;
- ret = sdw_transfer(slave->bus, slave, &msg);
- pm_runtime_put(slave->bus->dev);
- return ret;
+} +EXPORT_SYMBOL(sdw_nread);
+/**
- sdw_nwrite() - Write "n" contiguous SDW Slave registers
- @slave: SDW Slave
- @addr: Register address
- @count: length
- @val: Buffer for values to be read
- */
+int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{
- struct sdw_msg msg;
- int ret;
- ret = sdw_fill_msg(&msg, slave, addr, count,
slave->dev_num, SDW_MSG_FLAG_WRITE, val);
- if (ret < 0)
return ret;
- ret = pm_runtime_get_sync(slave->bus->dev);
- if (!ret)
return ret;
- ret = sdw_transfer(slave->bus, slave, &msg);
- pm_runtime_put(slave->bus->dev);
- return ret;
+} +EXPORT_SYMBOL(sdw_nwrite);
+/**
- sdw_read() - Read a SDW Slave register
- @slave: SDW Slave
- @addr: Register address
- */
+int sdw_read(struct sdw_slave *slave, u32 addr) +{
- u8 buf;
- int ret;
- ret = sdw_nread(slave, addr, 1, &buf);
- if (ret < 0)
return ret;
- else
return buf;
+} +EXPORT_SYMBOL(sdw_read);
+/**
- sdw_write() - Write a SDW Slave register
- @slave: SDW Slave
- @addr: Register address
- @value: Register value
- */
+int sdw_write(struct sdw_slave *slave, u32 addr, u8 value) +{
- return sdw_nwrite(slave, addr, 1, &value);
+} +EXPORT_SYMBOL(sdw_write);
- void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id) {
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index a54921825ce0..bde5f840ca96 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -16,4 +16,41 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id);
+enum {
- SDW_MSG_FLAG_READ = 0,
- SDW_MSG_FLAG_WRITE,
+};
+/**
- struct sdw_msg - Message structure
- @addr: Register address accessed in the Slave
- @len: number of messages
- @dev_num: Slave device number
- @addr_page1: SCP address page 1 Slave register
- @addr_page2: SCP address page 2 Slave register
- @flags: transfer flags, indicate if xfer is read or write
- @buf: message data buffer
- @ssp_sync: Send message at SSP (Stream Synchronization Point)
- @page: address requires paging
- */
+struct sdw_msg {
- u16 addr;
- u16 len;
- u16 dev_num;
was there a reason for dev_num with 16 bits - you have 16 values max...
- u8 addr_page1;
- u8 addr_page2;
- u8 flags;
- u8 *buf;
- bool ssp_sync;
- bool page;
+};
+int sdw_transfer(struct sdw_bus *bus, struct sdw_slave *slave,
struct sdw_msg *msg);
+int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_slave *slave,
struct sdw_msg *msg, struct sdw_defer *defer);
+int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf);
- #endif /* __SDW_BUS_H */
diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index ddfae18f4306..d3337b5b882b 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -31,6 +31,27 @@ enum sdw_slave_status { SDW_SLAVE_RESERVED = 3, };
+/**
- enum sdw_command_response - Command response as defined by SDW spec
- @SDW_CMD_OK: cmd was successful
- @SDW_CMD_IGNORED: cmd was ignored
- @SDW_CMD_FAIL: cmd was NACKed
- @SDW_CMD_TIMEOUT: cmd timedout
- @SDW_CMD_FAIL_OTHER: cmd failed due to other reason than above
- NOTE: The enum is different than actual Spec as response in the Spec is
- combination of ACK/NAK bits
- SDW_CMD_TIMEOUT/FAIL_OTHER is defined for SW use, not in spec
- */
+enum sdw_command_response {
- SDW_CMD_OK = 0,
- SDW_CMD_IGNORED = 1,
- SDW_CMD_FAIL = 2,
- SDW_CMD_TIMEOUT = 4,
- SDW_CMD_FAIL_OTHER = 8,
Humm, I can't recall if/why this is a mask? does it need to be?
+};
- /*
*/
- SDW properties, defined in MIPI DisCo spec v1.0
@@ -359,12 +380,37 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
- SDW master structures and APIs
*/
+struct sdw_msg;
+/**
- struct sdw_defer - SDW deffered message
- @length: message length
- @complete: message completion
- @msg: SDW message
- */
+struct sdw_defer {
- int length;
- struct completion complete;
- struct sdw_msg *msg;
+};
/**
- struct sdw_master_ops - Master driver ops
- @read_prop: Read Master properties
- @xfer_msg: Transfer message callback
- @xfer_msg_defer: Defer version of transfer message callback
- @reset_page_addr: Reset the SCP page address registers
*/ struct sdw_master_ops { int (*read_prop)(struct sdw_bus *bus);
enum sdw_command_response (*xfer_msg)
(struct sdw_bus *bus, struct sdw_msg *msg);
enum sdw_command_response (*xfer_msg_defer)
(struct sdw_bus *bus, struct sdw_msg *msg,
struct sdw_defer *defer);
enum sdw_command_response (*reset_page_addr)
(struct sdw_bus *bus, unsigned int dev_num);
};
/**
@@ -375,8 +421,10 @@ struct sdw_master_ops {
- @assigned: Bitmap for Slave device numbers.
- Bit set implies used number, bit clear implies unused number.
- @bus_lock: bus lock
- @msg_lock: message lock
- @ops: Master callback ops
- @prop: Master properties
*/ struct sdw_bus {
- @defer_msg: Defer message
- @clk_stop_timeout: Clock stop timeout computed
@@ -385,12 +433,21 @@ struct sdw_bus { struct list_head slaves; DECLARE_BITMAP(assigned, SDW_MAX_DEVICES); struct mutex bus_lock;
struct mutex msg_lock; const struct sdw_master_ops *ops; struct sdw_master_prop prop;
struct sdw_defer defer_msg; unsigned int clk_stop_timeout; };
int sdw_add_bus_master(struct sdw_bus *bus); void sdw_delete_bus_master(struct sdw_bus *bus);
+/* messaging and data APIs */
+int sdw_read(struct sdw_slave *slave, u32 addr); +int sdw_write(struct sdw_slave *slave, u32 addr, u8 value); +int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val); +int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val);
- #endif /* __SOUNDWIRE_H */
On Fri, Dec 01, 2017 at 05:27:31PM -0600, Pierre-Louis Bossart wrote:
+static inline int find_response_code(enum sdw_command_response resp) +{
- switch (resp) {
- case SDW_CMD_OK:
return 0;
- case SDW_CMD_IGNORED:
return -ENODATA;
- case SDW_CMD_TIMEOUT:
return -ETIMEDOUT;
- default:
return -EIO;
the 'default' case will handle both SDW_CMD_FAIL (which is a bus event usually due to bus clash or parity issues) and SDW_CMD_FAIL_OTHER (which is an imp-def IP event).
Do they really belong in the same basket? From a debug perspective there is quite a bit of information lost.
at higher level the error handling is same. the information is not lost as it is expected that you would log it at error source.
+static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{
- int retry = bus->prop.err_threshold;
- enum sdw_command_response resp;
- int ret = 0, i;
- for (i = 0; i <= retry; i++) {
resp = bus->ops->xfer_msg(bus, msg);
ret = find_response_code(resp);
/* if cmd is ok or ignored return */
if (ret == 0 || ret == -ENODATA)
Can you document why you don't retry on a CMD_IGNORED? I know there was a reason, I just can't remember it.
CMD_IGNORED can be okay on broadcast. User of this API can retry all they want!
Now that I think of it, the retry on TIMEOUT makes no sense to me. The retry was intended for bus-level issues, where maybe a single bit error causes an issue without consequences, but the TIMEOUT is a completely different beast, it's the master IP that doesn't answer really, a completely different case.
well in those cases where you have blue wires, it actually helps :)
+/**
- sdw_transfer() - Synchronous transfer message to a SDW Slave device
- @bus: SDW bus
- @slave: SDW Slave
is this just me or this argument is not used?
That's what happens where API gets reworked umpteen times, thanks for pointing. Earlier slave was required to get the page address calculation, now that it is removed, it is no longer required !
+int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf)
+{
- memset(msg, 0, sizeof(*msg));
- msg->addr = addr;
add comment on implicit truncation to 16-bit address
Sure..
- msg->len = count;
- msg->dev_num = dev_num;
- msg->flags = flags;
- msg->buf = buf;
- msg->ssp_sync = false;
- msg->page = false;
- if (addr < SDW_REG_NO_PAGE) { /* no paging area */
return 0;
- } else if (addr >= SDW_REG_MAX) { /* illegal addr */
pr_err("SDW: Invalid address %x passed\n", addr);
return -EINVAL;
- }
- if (addr < SDW_REG_OPTIONAL_PAGE) { /* 32k but no page */
if (slave && !slave->prop.paging_support)
return 0;
/* no need for else as that will fall thru to paging */
- }
- /* paging madatory */
mandatory
thanks for spotting
- if (dev_num == SDW_ENUM_DEV_NUM || dev_num == SDW_BROADCAST_DEV_NUM) {
pr_err("SDW: Invalid device for paging :%d\n", dev_num);
return -EINVAL;
- }
- if (!slave) {
pr_err("SDW: No slave for paging addr\n");
return -EINVAL;
I would move this test up, since if you have a NULL slave you should return an error in all case, otherwise there will be an oops in the code below ...
naah, this fn is called for all IO, like broadcast where we have no slave. So it is really optional for API, but for paging it is mandatory!
- } else if (!slave->prop.paging_support) {
this wont oops as slave null would never come here
dev_err(&slave->dev,
"address %x needs paging but no support", addr);
return -EINVAL;
- }
- msg->addr_page1 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE1_MASK));
- msg->addr_page2 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE2_MASK));
- msg->addr |= BIT(15);
- msg->page = true;
looks ok :-)
finally !!! yeah the paging and IO code has given me most headache till now!
+int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{
- struct sdw_msg msg;
- int ret;
- ret = sdw_fill_msg(&msg, slave, addr, count,
slave->dev_num, SDW_MSG_FLAG_READ, val);
- if (ret < 0)
return ret;
... if you don't test for the slave argument in the sdw_fill_msg but the address is correct then the rest of the code will bomb out.
I dont think so..
+struct sdw_msg {
- u16 addr;
- u16 len;
- u16 dev_num;
was there a reason for dev_num with 16 bits - you have 16 values max...
cant remember, we should use lesser bits though.
+enum sdw_command_response {
- SDW_CMD_OK = 0,
- SDW_CMD_IGNORED = 1,
- SDW_CMD_FAIL = 2,
- SDW_CMD_TIMEOUT = 4,
- SDW_CMD_FAIL_OTHER = 8,
Humm, I can't recall if/why this is a mask? does it need to be?
mask, not following!
Taking a wild guess that you are asking about last error, which is for SW errors like malloc fail etc...
On 12/3/17 11:04 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:27:31PM -0600, Pierre-Louis Bossart wrote:
+static inline int find_response_code(enum sdw_command_response resp) +{
- switch (resp) {
- case SDW_CMD_OK:
return 0;
- case SDW_CMD_IGNORED:
return -ENODATA;
- case SDW_CMD_TIMEOUT:
return -ETIMEDOUT;
- default:
return -EIO;
the 'default' case will handle both SDW_CMD_FAIL (which is a bus event usually due to bus clash or parity issues) and SDW_CMD_FAIL_OTHER (which is an imp-def IP event).
Do they really belong in the same basket? From a debug perspective there is quite a bit of information lost.
at higher level the error handling is same. the information is not lost as it is expected that you would log it at error source.
I don't understand this. It's certainly not the same for me if you detect an electric problem or if the IP is in the weeds. Logging at the source is fine but this filtering prevents higher levels from doing anything different.
+static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{
- int retry = bus->prop.err_threshold;
- enum sdw_command_response resp;
- int ret = 0, i;
- for (i = 0; i <= retry; i++) {
resp = bus->ops->xfer_msg(bus, msg);
ret = find_response_code(resp);
/* if cmd is ok or ignored return */
if (ret == 0 || ret == -ENODATA)
Can you document why you don't retry on a CMD_IGNORED? I know there was a reason, I just can't remember it.
CMD_IGNORED can be okay on broadcast. User of this API can retry all they want!
So you retry if this is a CMD_FAILED but let higher levels retry for CMD_IGNORED, sorry I don't see the logic.
Now that I think of it, the retry on TIMEOUT makes no sense to me. The retry was intended for bus-level issues, where maybe a single bit error causes an issue without consequences, but the TIMEOUT is a completely different beast, it's the master IP that doesn't answer really, a completely different case.
well in those cases where you have blue wires, it actually helps :)
Blue wires are not supposed to change electrical behavior. TIMEOUT is only an internal SOC level issue, so no I don't get how this helps.
You have a retry count that is provided in the BIOS/firmware through disco properties and it's meant to bus errors. You are abusing the definitions. A command failed is supposed to be detected at the frame rate, which is typically 20us. a timeout is likely a 100s of ms value, so if you retry on top it's going to lock up the bus.
+/**
- sdw_transfer() - Synchronous transfer message to a SDW Slave device
- @bus: SDW bus
- @slave: SDW Slave
is this just me or this argument is not used?
That's what happens where API gets reworked umpteen times, thanks for pointing. Earlier slave was required to get the page address calculation, now that it is removed, it is no longer required !
+int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf)
+{
- memset(msg, 0, sizeof(*msg));
- msg->addr = addr;
add comment on implicit truncation to 16-bit address
Sure..
- msg->len = count;
- msg->dev_num = dev_num;
- msg->flags = flags;
- msg->buf = buf;
- msg->ssp_sync = false;
- msg->page = false;
- if (addr < SDW_REG_NO_PAGE) { /* no paging area */
return 0;
- } else if (addr >= SDW_REG_MAX) { /* illegal addr */
pr_err("SDW: Invalid address %x passed\n", addr);
return -EINVAL;
- }
- if (addr < SDW_REG_OPTIONAL_PAGE) { /* 32k but no page */
if (slave && !slave->prop.paging_support)
return 0;
/* no need for else as that will fall thru to paging */
- }
- /* paging madatory */
mandatory
thanks for spotting
- if (dev_num == SDW_ENUM_DEV_NUM || dev_num == SDW_BROADCAST_DEV_NUM) {
pr_err("SDW: Invalid device for paging :%d\n", dev_num);
return -EINVAL;
- }
- if (!slave) {
pr_err("SDW: No slave for paging addr\n");
return -EINVAL;
I would move this test up, since if you have a NULL slave you should return an error in all case, otherwise there will be an oops in the code below ...
naah, this fn is called for all IO, like broadcast where we have no slave. So it is really optional for API, but for paging it is mandatory!
- } else if (!slave->prop.paging_support) {
this wont oops as slave null would never come here
dev_err(&slave->dev,
"address %x needs paging but no support", addr);
return -EINVAL;
- }
- msg->addr_page1 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE1_MASK));
- msg->addr_page2 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE2_MASK));
- msg->addr |= BIT(15);
- msg->page = true;
looks ok :-)
finally !!! yeah the paging and IO code has given me most headache till now!
+int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{
- struct sdw_msg msg;
- int ret;
- ret = sdw_fill_msg(&msg, slave, addr, count,
slave->dev_num, SDW_MSG_FLAG_READ, val);
- if (ret < 0)
return ret;
... if you don't test for the slave argument in the sdw_fill_msg but the address is correct then the rest of the code will bomb out.
I dont think so..
Actually you are right, this makes no sense to test for a null slave because you are already dead.
+int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_READ, val);
the dev_num indirection is already killing you.
+ if (ret < 0) + return ret; + + ret = pm_runtime_get_sync(slave->bus->dev); + if (!ret) + return ret;
+struct sdw_msg {
- u16 addr;
- u16 len;
- u16 dev_num;
was there a reason for dev_num with 16 bits - you have 16 values max...
cant remember, we should use lesser bits though.
+enum sdw_command_response {
- SDW_CMD_OK = 0,
- SDW_CMD_IGNORED = 1,
- SDW_CMD_FAIL = 2,
- SDW_CMD_TIMEOUT = 4,
- SDW_CMD_FAIL_OTHER = 8,
Humm, I can't recall if/why this is a mask? does it need to be?
mask, not following!
Taking a wild guess that you are asking about last error, which is for SW errors like malloc fail etc...
no, I was asking why this is declared as if it was used for a bitmask, why not 0,1,2,3,4?
On Sun, Dec 03, 2017 at 09:01:41PM -0600, Pierre-Louis Bossart wrote:
On 12/3/17 11:04 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:27:31PM -0600, Pierre-Louis Bossart wrote:
Sorry looks like I missed replying to this one earlier.
+static inline int find_response_code(enum sdw_command_response resp) +{
- switch (resp) {
- case SDW_CMD_OK:
return 0;
- case SDW_CMD_IGNORED:
return -ENODATA;
- case SDW_CMD_TIMEOUT:
return -ETIMEDOUT;
- default:
return -EIO;
the 'default' case will handle both SDW_CMD_FAIL (which is a bus event usually due to bus clash or parity issues) and SDW_CMD_FAIL_OTHER (which is an imp-def IP event).
Do they really belong in the same basket? From a debug perspective there is quite a bit of information lost.
at higher level the error handling is same. the information is not lost as it is expected that you would log it at error source.
I don't understand this. It's certainly not the same for me if you detect an electric problem or if the IP is in the weeds. Logging at the source is fine but this filtering prevents higher levels from doing anything different.
The point is higher levels like here cant do much than bail out and complain.
Can you point out what would be different behaviour in each of these cases?
+static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{
- int retry = bus->prop.err_threshold;
- enum sdw_command_response resp;
- int ret = 0, i;
- for (i = 0; i <= retry; i++) {
resp = bus->ops->xfer_msg(bus, msg);
ret = find_response_code(resp);
/* if cmd is ok or ignored return */
if (ret == 0 || ret == -ENODATA)
Can you document why you don't retry on a CMD_IGNORED? I know there was a reason, I just can't remember it.
CMD_IGNORED can be okay on broadcast. User of this API can retry all they want!
So you retry if this is a CMD_FAILED but let higher levels retry for CMD_IGNORED, sorry I don't see the logic.
Yes that is right.
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
But if I am not expecting a CMD_IGNORED response, I can very well go ahead and retry from caller. The context is with caller and they can choose to do appropriate handling.
And I have clarified this couple of times to you already, not sure how many more times I would have to do that.
Now that I think of it, the retry on TIMEOUT makes no sense to me. The retry was intended for bus-level issues, where maybe a single bit error causes an issue without consequences, but the TIMEOUT is a completely different beast, it's the master IP that doesn't answer really, a completely different case.
well in those cases where you have blue wires, it actually helps :)
Blue wires are not supposed to change electrical behavior. TIMEOUT is only an internal SOC level issue, so no I don't get how this helps.
You have a retry count that is provided in the BIOS/firmware through disco properties and it's meant to bus errors. You are abusing the definitions. A command failed is supposed to be detected at the frame rate, which is typically 20us. a timeout is likely a 100s of ms value, so if you retry on top it's going to lock up the bus.
The world is not perfect! A guy debugging setups needs all the help. I do not see any reason for not to retry. Bus is anyway locked up while a transfer is ongoing (we serialize transfers).
Now if you feel this should be abhorred, I can change this for timeout.
+enum sdw_command_response {
- SDW_CMD_OK = 0,
- SDW_CMD_IGNORED = 1,
- SDW_CMD_FAIL = 2,
- SDW_CMD_TIMEOUT = 4,
- SDW_CMD_FAIL_OTHER = 8,
Humm, I can't recall if/why this is a mask? does it need to be?
mask, not following!
Taking a wild guess that you are asking about last error, which is for SW errors like malloc fail etc...
no, I was asking why this is declared as if it was used for a bitmask, why not 0,1,2,3,4?
Oh okay, I think it was something to do with bits for errors, but don see it helping so I can change it either way...
On 12/5/17 12:31 AM, Vinod Koul wrote:
On Sun, Dec 03, 2017 at 09:01:41PM -0600, Pierre-Louis Bossart wrote:
On 12/3/17 11:04 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:27:31PM -0600, Pierre-Louis Bossart wrote:
Sorry looks like I missed replying to this one earlier.
+static inline int find_response_code(enum sdw_command_response resp) +{
- switch (resp) {
- case SDW_CMD_OK:
return 0;
- case SDW_CMD_IGNORED:
return -ENODATA;
- case SDW_CMD_TIMEOUT:
return -ETIMEDOUT;
- default:
return -EIO;
the 'default' case will handle both SDW_CMD_FAIL (which is a bus event usually due to bus clash or parity issues) and SDW_CMD_FAIL_OTHER (which is an imp-def IP event).
Do they really belong in the same basket? From a debug perspective there is quite a bit of information lost.
at higher level the error handling is same. the information is not lost as it is expected that you would log it at error source.
I don't understand this. It's certainly not the same for me if you detect an electric problem or if the IP is in the weeds. Logging at the source is fine but this filtering prevents higher levels from doing anything different.
The point is higher levels like here cant do much than bail out and complain.
Can you point out what would be different behaviour in each of these cases?
+static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{
- int retry = bus->prop.err_threshold;
- enum sdw_command_response resp;
- int ret = 0, i;
- for (i = 0; i <= retry; i++) {
resp = bus->ops->xfer_msg(bus, msg);
ret = find_response_code(resp);
/* if cmd is ok or ignored return */
if (ret == 0 || ret == -ENODATA)
Can you document why you don't retry on a CMD_IGNORED? I know there was a reason, I just can't remember it.
CMD_IGNORED can be okay on broadcast. User of this API can retry all they want!
So you retry if this is a CMD_FAILED but let higher levels retry for CMD_IGNORED, sorry I don't see the logic.
Yes that is right.
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
But if I am not expecting a CMD_IGNORED response, I can very well go ahead and retry from caller. The context is with caller and they can choose to do appropriate handling.
And I have clarified this couple of times to you already, not sure how many more times I would have to do that.
Until you clarify what you are doing. There is ONE case where IGNORED is a valid answer (reading the Prepare not finished bits), and it should not only be documented but analyzed in more details. For a write an IGNORED is never OK.
Now that I think of it, the retry on TIMEOUT makes no sense to me. The retry was intended for bus-level issues, where maybe a single bit error causes an issue without consequences, but the TIMEOUT is a completely different beast, it's the master IP that doesn't answer really, a completely different case.
well in those cases where you have blue wires, it actually helps :)
Blue wires are not supposed to change electrical behavior. TIMEOUT is only an internal SOC level issue, so no I don't get how this helps.
You have a retry count that is provided in the BIOS/firmware through disco properties and it's meant to bus errors. You are abusing the definitions. A command failed is supposed to be detected at the frame rate, which is typically 20us. a timeout is likely a 100s of ms value, so if you retry on top it's going to lock up the bus.
The world is not perfect! A guy debugging setups needs all the help. I do not see any reason for not to retry. Bus is anyway locked up while a transfer is ongoing (we serialize transfers).
Now if you feel this should be abhorred, I can change this for timeout.
This TIMEOUT thing is your own definition, it's not part of the spec, so I don't see how it can be lumped together with spec-related parts.
It's fine to keep a retry but please document what the expectations are for the TIMEOUT case.
+enum sdw_command_response {
- SDW_CMD_OK = 0,
- SDW_CMD_IGNORED = 1,
- SDW_CMD_FAIL = 2,
- SDW_CMD_TIMEOUT = 4,
- SDW_CMD_FAIL_OTHER = 8,
Humm, I can't recall if/why this is a mask? does it need to be?
mask, not following!
Taking a wild guess that you are asking about last error, which is for SW errors like malloc fail etc...
no, I was asking why this is declared as if it was used for a bitmask, why not 0,1,2,3,4?
Oh okay, I think it was something to do with bits for errors, but don see it helping so I can change it either way...
Unless you use bit-wise operators and combined responses there is no reason to keep the current definitions.
On 12/5/17 7:43 AM, Pierre-Louis Bossart wrote:
On 12/5/17 12:31 AM, Vinod Koul wrote:
On Sun, Dec 03, 2017 at 09:01:41PM -0600, Pierre-Louis Bossart wrote:
On 12/3/17 11:04 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:27:31PM -0600, Pierre-Louis Bossart wrote:
Sorry looks like I missed replying to this one earlier.
+static inline int find_response_code(enum sdw_command_response resp) +{ +Â Â Â switch (resp) { +Â Â Â case SDW_CMD_OK: +Â Â Â Â Â Â Â return 0;
+Â Â Â case SDW_CMD_IGNORED: +Â Â Â Â Â Â Â return -ENODATA;
+Â Â Â case SDW_CMD_TIMEOUT: +Â Â Â Â Â Â Â return -ETIMEDOUT;
+Â Â Â default: +Â Â Â Â Â Â Â return -EIO;
the 'default' case will handle both SDW_CMD_FAIL (which is a bus event usually due to bus clash or parity issues) and SDW_CMD_FAIL_OTHER (which is an imp-def IP event).
Do they really belong in the same basket? From a debug perspective there is quite a bit of information lost.
at higher level the error handling is same. the information is not lost as it is expected that you would log it at error source.
I don't understand this. It's certainly not the same for me if you detect an electric problem or if the IP is in the weeds. Logging at the source is fine but this filtering prevents higher levels from doing anything different.
The point is higher levels like here cant do much than bail out and complain.
Can you point out what would be different behaviour in each of these cases?
+static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{ +Â Â Â int retry = bus->prop.err_threshold; +Â Â Â enum sdw_command_response resp; +Â Â Â int ret = 0, i;
+Â Â Â for (i = 0; i <= retry; i++) { +Â Â Â Â Â Â Â resp = bus->ops->xfer_msg(bus, msg); +Â Â Â Â Â Â Â ret = find_response_code(resp);
+Â Â Â Â Â Â Â /* if cmd is ok or ignored return */ +Â Â Â Â Â Â Â if (ret == 0 || ret == -ENODATA)
Can you document why you don't retry on a CMD_IGNORED? I know there was a reason, I just can't remember it.
CMD_IGNORED can be okay on broadcast. User of this API can retry all they want!
So you retry if this is a CMD_FAILED but let higher levels retry for CMD_IGNORED, sorry I don't see the logic.
Yes that is right.
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
But if I am not expecting a CMD_IGNORED response, I can very well go ahead and retry from caller. The context is with caller and they can choose to do appropriate handling.
And I have clarified this couple of times to you already, not sure how many more times I would have to do that.
Until you clarify what you are doing. There is ONE case where IGNORED is a valid answer (reading the Prepare not finished bits), and it should not only be documented but analyzed in more details.
I meant Read SCP_DevID registers from Device0... prepare bits should never return a CMD_IGNORED
For a write an IGNORED is never OK.
Now that I think of it, the retry on TIMEOUT makes no sense to me. The retry was intended for bus-level issues, where maybe a single bit error causes an issue without consequences, but the TIMEOUT is a completely different beast, it's the master IP that doesn't answer really, a completely different case.
well in those cases where you have blue wires, it actually helps :)
Blue wires are not supposed to change electrical behavior. TIMEOUT is only an internal SOC level issue, so no I don't get how this helps.
You have a retry count that is provided in the BIOS/firmware through disco properties and it's meant to bus errors. You are abusing the definitions. A command failed is supposed to be detected at the frame rate, which is typically 20us. a timeout is likely a 100s of ms value, so if you retry on top it's going to lock up the bus.
The world is not perfect! A guy debugging setups needs all the help. I do not see any reason for not to retry. Bus is anyway locked up while a transfer is ongoing (we serialize transfers).
Now if you feel this should be abhorred, I can change this for timeout.
This TIMEOUT thing is your own definition, it's not part of the spec, so I don't see how it can be lumped together with spec-related parts.
It's fine to keep a retry but please document what the expectations are for the TIMEOUT case.
+enum sdw_command_response { +Â Â Â SDW_CMD_OK = 0, +Â Â Â SDW_CMD_IGNORED = 1, +Â Â Â SDW_CMD_FAIL = 2, +Â Â Â SDW_CMD_TIMEOUT = 4, +Â Â Â SDW_CMD_FAIL_OTHER = 8,
Humm, I can't recall if/why this is a mask? does it need to be?
mask, not following!
Taking a wild guess that you are asking about last error, which is for SW errors like malloc fail etc...
no, I was asking why this is declared as if it was used for a bitmask, why not 0,1,2,3,4?
Oh okay, I think it was something to do with bits for errors, but don see it helping so I can change it either way...
Unless you use bit-wise operators and combined responses there is no reason to keep the current definitions.
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
On Tue, Dec 05, 2017 at 08:48:03AM -0600, Pierre-Louis Bossart wrote:
On 12/5/17 7:43 AM, Pierre-Louis Bossart wrote:
On 12/5/17 12:31 AM, Vinod Koul wrote:
On Sun, Dec 03, 2017 at 09:01:41PM -0600, Pierre-Louis Bossart wrote:
>+static inline int do_transfer(struct sdw_bus *bus, struct >sdw_msg *msg) >+{ >+Â Â Â int retry = bus->prop.err_threshold; >+Â Â Â enum sdw_command_response resp; >+Â Â Â int ret = 0, i; >+ >+Â Â Â for (i = 0; i <= retry; i++) { >+Â Â Â Â Â Â Â resp = bus->ops->xfer_msg(bus, msg); >+Â Â Â Â Â Â Â ret = find_response_code(resp); >+ >+Â Â Â Â Â Â Â /* if cmd is ok or ignored return */ >+Â Â Â Â Â Â Â if (ret == 0 || ret == -ENODATA)
Can you document why you don't retry on a CMD_IGNORED? I know there was a reason, I just can't remember it.
CMD_IGNORED can be okay on broadcast. User of this API can retry all they want!
So you retry if this is a CMD_FAILED but let higher levels retry for CMD_IGNORED, sorry I don't see the logic.
Yes that is right.
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
Above is the clarfication
But if I am not expecting a CMD_IGNORED response, I can very well go ahead and retry from caller. The context is with caller and they can choose to do appropriate handling.
And I have clarified this couple of times to you already, not sure how many more times I would have to do that.
Until you clarify what you are doing.
Let me try again, I think u missed that part of my reply above
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
There is ONE case where IGNORED is a valid answer (reading the Prepare not finished bits), and it should not only be documented but analyzed in more details.
I meant Read SCP_DevID registers from Device0... prepare bits should never return a CMD_IGNORED
Precisely as I pointed out above.
For a write an IGNORED is never OK.
Agreed, but then transfer does both read and write. Write call can treat it as error and read call leaves it upto caller.
Does that sound okay for you?
On 12/5/17 11:58 PM, Vinod Koul wrote:
On Tue, Dec 05, 2017 at 08:48:03AM -0600, Pierre-Louis Bossart wrote:
On 12/5/17 7:43 AM, Pierre-Louis Bossart wrote:
On 12/5/17 12:31 AM, Vinod Koul wrote:
On Sun, Dec 03, 2017 at 09:01:41PM -0600, Pierre-Louis Bossart wrote:
>> +static inline int do_transfer(struct sdw_bus *bus, struct >> sdw_msg *msg) >> +{ >> +Â Â Â int retry = bus->prop.err_threshold; >> +Â Â Â enum sdw_command_response resp; >> +Â Â Â int ret = 0, i; >> + >> +Â Â Â for (i = 0; i <= retry; i++) { >> +Â Â Â Â Â Â Â resp = bus->ops->xfer_msg(bus, msg); >> +Â Â Â Â Â Â Â ret = find_response_code(resp); >> + >> +Â Â Â Â Â Â Â /* if cmd is ok or ignored return */ >> +Â Â Â Â Â Â Â if (ret == 0 || ret == -ENODATA) > > Can you document why you don't retry on a CMD_IGNORED? I know > there was a > reason, I just can't remember it.
CMD_IGNORED can be okay on broadcast. User of this API can retry all they want!
So you retry if this is a CMD_FAILED but let higher levels retry for CMD_IGNORED, sorry I don't see the logic.
Yes that is right.
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
Above is the clarfication
But if I am not expecting a CMD_IGNORED response, I can very well go ahead and retry from caller. The context is with caller and they can choose to do appropriate handling.
And I have clarified this couple of times to you already, not sure how many more times I would have to do that.
Until you clarify what you are doing.
Let me try again, I think u missed that part of my reply above
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
There is ONE case where IGNORED is a valid answer (reading the Prepare not finished bits), and it should not only be documented but analyzed in more details.
I meant Read SCP_DevID registers from Device0... prepare bits should never return a CMD_IGNORED
Precisely as I pointed out above.
For a write an IGNORED is never OK.
Agreed, but then transfer does both read and write. Write call can treat it as error and read call leaves it upto caller.
Does that sound okay for you?
Not really. You have one case where it's ok and all others are not ok, to me this sounds like you should avoid the retry at the lowest level rather than pushing this to the caller And now that I think of it, the definitions for the DisCo spec were really meant for hardware-based retries available in some master IPs. If this is enabled, then the loops in software are not really needed at all. Can you please check this point?
On Wed, Dec 06, 2017 at 07:32:43AM -0600, Pierre-Louis Bossart wrote:
On 12/5/17 11:58 PM, Vinod Koul wrote:
On Tue, Dec 05, 2017 at 08:48:03AM -0600, Pierre-Louis Bossart wrote:
On 12/5/17 7:43 AM, Pierre-Louis Bossart wrote:
On 12/5/17 12:31 AM, Vinod Koul wrote:
On Sun, Dec 03, 2017 at 09:01:41PM -0600, Pierre-Louis Bossart wrote:
>>>+static inline int do_transfer(struct sdw_bus *bus, struct >>>sdw_msg *msg) >>>+{ >>>+Â Â Â int retry = bus->prop.err_threshold; >>>+Â Â Â enum sdw_command_response resp; >>>+Â Â Â int ret = 0, i; >>>+ >>>+Â Â Â for (i = 0; i <= retry; i++) { >>>+Â Â Â Â Â Â Â resp = bus->ops->xfer_msg(bus, msg); >>>+Â Â Â Â Â Â Â ret = find_response_code(resp); >>>+ >>>+Â Â Â Â Â Â Â /* if cmd is ok or ignored return */ >>>+Â Â Â Â Â Â Â if (ret == 0 || ret == -ENODATA) >> >>Can you document why you don't retry on a CMD_IGNORED? I know >>there was a >>reason, I just can't remember it. > >CMD_IGNORED can be okay on broadcast. User of this API can retry all >they >want!
So you retry if this is a CMD_FAILED but let higher levels retry for CMD_IGNORED, sorry I don't see the logic.
Yes that is right.
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
Above is the clarfication
But if I am not expecting a CMD_IGNORED response, I can very well go ahead and retry from caller. The context is with caller and they can choose to do appropriate handling.
And I have clarified this couple of times to you already, not sure how many more times I would have to do that.
Until you clarify what you are doing.
Let me try again, I think u missed that part of my reply above
If I am doing a broadcast read, lets say for Device Id registers, why in the world would I want to retry? CMD_IGNORED is a valid response and required to stop enumeration cycle in that case.
There is ONE case where IGNORED is a valid answer (reading the Prepare not finished bits), and it should not only be documented but analyzed in more details.
I meant Read SCP_DevID registers from Device0... prepare bits should never return a CMD_IGNORED
Precisely as I pointed out above.
For a write an IGNORED is never OK.
Agreed, but then transfer does both read and write. Write call can treat it as error and read call leaves it upto caller.
Does that sound okay for you?
Not really. You have one case where it's ok and all others are not ok, to me this sounds like you should avoid the retry at the lowest level rather than pushing this to the caller And now that I think of it, the definitions for the DisCo spec were really meant for hardware-based retries available in some master IPs. If this is enabled, then the loops in software are not really needed at all. Can you please check this point?
Sure I will check that
SoundWire bus provides sdw_read() and sdw_write() APIs for Slave devices to program the registers. Provide support in regmap for SoundWire bus.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/base/regmap/Kconfig | 4 ++ drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-sdw.c | 92 ++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/Kconfig | 1 + include/linux/regmap.h | 37 ++++++++++++++++ 5 files changed, 135 insertions(+) create mode 100644 drivers/base/regmap/regmap-sdw.c
diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index 3a1535d812d8..71dc88a48cfa 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -41,3 +41,7 @@ config REGMAP_IRQ
config REGMAP_HWSPINLOCK bool + +config REGMAP_SOUNDWIRE + tristate + depends on SOUNDWIRE_BUS diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 0d298c446108..22d263cca395 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o obj-$(CONFIG_REGMAP_W1) += regmap-w1.o +obj-$(CONFIG_REGMAP_SOUNDWIRE) += regmap-sdw.o diff --git a/drivers/base/regmap/regmap-sdw.c b/drivers/base/regmap/regmap-sdw.c new file mode 100644 index 000000000000..3a72039e94ea --- /dev/null +++ b/drivers/base/regmap/regmap-sdw.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2015-17 Intel Corporation. + +#include <linux/device.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/soundwire/sdw.h> +#include "internal.h" + +static int regmap_sdw_write(void *context, unsigned int reg, unsigned int val) +{ + struct device *dev = context; + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + return sdw_write(slave, reg, val); +} + +static int regmap_sdw_read(void *context, unsigned int reg, unsigned int *val) +{ + struct device *dev = context; + struct sdw_slave *slave = dev_to_sdw_dev(dev); + int read; + + read = sdw_read(slave, reg); + if (read < 0) + return read; + + *val = read; + return 0; +} + +static struct regmap_bus regmap_sdw = { + .reg_read = regmap_sdw_read, + .reg_write = regmap_sdw_write, + .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 bits wide */ + 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_DESCRIPTION("Regmap SoundWire Module"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index d7d3908f4913..92a2c3bad87f 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -18,5 +18,6 @@ comment "SoundWire Devices"
config SOUNDWIRE_BUS tristate + select REGMAP_SOUNDWIRE
endif diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 15eddc1353ba..97a9c6563bb1 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -30,6 +30,7 @@ struct regmap; struct regmap_range_cfg; struct regmap_field; struct snd_ac97; +struct sdw_slave;
/* An enum of all the supported cache types */ enum regcache_type { @@ -524,6 +525,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, @@ -561,6 +566,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 @@ -710,6 +719,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 @@ -839,6 +862,20 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); __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);
From: Sanyog Kale sanyog.r.kale@intel.com
SoundWire Slaves report status to bus. Add helpers to handle the status changes.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/bus.c | 202 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 14 +++ include/linux/soundwire/sdw.h | 1 + 3 files changed, 217 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 2f108f162905..09126ddd3cdd 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -372,6 +372,93 @@ int sdw_write(struct sdw_slave *slave, u32 addr, u8 value) } EXPORT_SYMBOL(sdw_write);
+/* + * SDW alert handling + */ + +/* called with bus_lock held */ +static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i) +{ + struct sdw_slave *slave = NULL; + + list_for_each_entry(slave, &bus->slaves, node) { + if (slave->dev_num == i) + return slave; + } + + return NULL; +} + +static int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id) +{ + + if ((slave->id.unique_id != id.unique_id) || + (slave->id.mfg_id != id.mfg_id) || + (slave->id.part_id != id.part_id) || + (slave->id.class_id != id.class_id)) + return -ENODEV; + + return 0; +} + +/* called with bus_lock held */ +static int sdw_get_device_num(struct sdw_slave *slave) +{ + int bit; + + bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES); + if (bit == SDW_MAX_DEVICES) { + bit = -ENODEV; + goto err; + } + + /* + * Do not update dev_num in Slave data structure here, + * Update once program dev_num is successful + */ + set_bit(bit, slave->bus->assigned); + +err: + return bit; +} + +static int sdw_assign_device_num(struct sdw_slave *slave) +{ + int ret, dev_num; + + /* check first if device number is assigned, if so reuse that */ + if (!slave->dev_num) { + mutex_lock(&slave->bus->bus_lock); + dev_num = sdw_get_device_num(slave); + mutex_unlock(&slave->bus->bus_lock); + if (dev_num < 0) { + dev_err(slave->bus->dev, "Get dev_num failed: %d", + dev_num); + return dev_num; + } + } else { + dev_info(slave->bus->dev, + "Slave already registered dev_num:%d", + slave->dev_num); + + /* Clear the slave->dev_num to transfer message on device 0 */ + dev_num = slave->dev_num; + slave->dev_num = 0; + + } + + ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num); + if (ret < 0) { + dev_err(&slave->dev, "Program device_num failed: %d", ret); + return ret; + } + + /* After xfer of msg, restore dev_num */ + slave->dev_num = dev_num; + + return 0; +} + void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id) { @@ -400,3 +487,118 @@ void sdw_extract_slave_id(struct sdw_bus *bus, id->unique_id, id->sdw_version);
} + +static int sdw_program_device_num(struct sdw_bus *bus) +{ + u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0}; + struct sdw_slave *slave, *_s; + struct sdw_slave_id id; + struct sdw_msg msg; + bool found = false; + int count = 0, ret; + u64 addr; + + /* No Slave, so use raw xfer api */ + ret = sdw_fill_msg(&msg, NULL, SDW_SCP_DEVID_0, + SDW_NUM_DEV_ID_REGISTERS, 0, SDW_MSG_FLAG_READ, buf); + if (ret < 0) + return ret; + + do { + ret = sdw_transfer(bus, NULL, &msg); + if (ret == -ENODATA) { /* end of device id reads */ + ret = 0; + break; + } + if (ret < 0) { + dev_err(bus->dev, "DEVID read fail:%d\n", ret); + break; + } + + /* + * Construct the addr and extract. Cast the higher shift + * bits to avoid truncation due to size limit. + */ + addr = buf[5] | (buf[4] << 8) | (buf[3] << 16) | + (buf[2] << 24) | ((unsigned long long)buf[1] << 32) | + ((unsigned long long)buf[0] << 40); + + sdw_extract_slave_id(bus, addr, &id); + + /* Now compare with entries */ + list_for_each_entry_safe(slave, _s, &bus->slaves, node) { + if (sdw_compare_devid(slave, id) == 0) { + found = true; + + /* + * Assign a new dev_num to this Slave and + * not mark it present. It will be marked + * present after it reports ATTACHED on new + * dev_num + */ + ret = sdw_assign_device_num(slave); + if (ret) { + dev_err(slave->bus->dev, + "Assign dev_num failed:%d", + ret); + return ret; + } + + break; + } + } + + if (found == false) { + /* TODO: Park this device in Group 13 */ + dev_err(bus->dev, "Slave Entry not found"); + } + + count++; + + } while (ret == 0 && count < (SDW_MAX_DEVICES * 2)); + + return ret; +} + +static void sdw_modify_slave_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + mutex_lock(&slave->bus->bus_lock); + slave->status = status; + mutex_unlock(&slave->bus->bus_lock); +} + +static int sdw_initialize_slave(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int ret; + u8 val; + + /* Set bus clash and parity interrupt mask */ + val = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + + /* Enable SCP interrupts */ + ret = sdw_update(slave, SDW_SCP_INTMASK1, val, val); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INTMASK1 write failed:%d", ret); + return ret; + } + + /* No need to continue if DP0 is not present */ + if (!slave->prop.dp0_prop) + return 0; + + /* Enable DP0 interrupts */ + val = prop->dp0_prop->device_interrupts; + val |= SDW_DP0_INT_PORT_READY | SDW_DP0_INT_BRA_FAILURE; + + ret = sdw_update(slave, SDW_DP0_INTMASK, val, val); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INTMASK read failed:%d", ret); + return val; + } + + return 0; +} diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index bde5f840ca96..b491e86f2c4c 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -53,4 +53,18 @@ int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_slave *slave, int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf);
+/* Read-Modify-Write Slave register */ +static inline int +sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) +{ + int tmp; + + tmp = sdw_read(slave, addr); + if (tmp < 0) + return tmp; + + tmp = (tmp & ~mask) | val; + return sdw_write(slave, addr, tmp); +} + #endif /* __SDW_BUS_H */ diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index d3337b5b882b..09619d042909 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -15,6 +15,7 @@ struct sdw_slave; /* SDW Enumeration Device Number */ #define SDW_ENUM_DEV_NUM 0
+#define SDW_NUM_DEV_ID_REGISTERS 6 #define SDW_MAX_DEVICES 11
/**
On 12/1/17 3:56 AM, Vinod Koul wrote:
From: Sanyog Kale sanyog.r.kale@intel.com
SoundWire Slaves report status to bus. Add helpers to handle the status changes.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/bus.c | 202 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 14 +++ include/linux/soundwire/sdw.h | 1 + 3 files changed, 217 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 2f108f162905..09126ddd3cdd 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -372,6 +372,93 @@ int sdw_write(struct sdw_slave *slave, u32 addr, u8 value) } EXPORT_SYMBOL(sdw_write);
+/*
- SDW alert handling
- */
+/* called with bus_lock held */ +static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i) +{
- struct sdw_slave *slave = NULL;
- list_for_each_entry(slave, &bus->slaves, node) {
if (slave->dev_num == i)
return slave;
- }
- return NULL;
+}
+static int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id) +{
- if ((slave->id.unique_id != id.unique_id) ||
(slave->id.mfg_id != id.mfg_id) ||
(slave->id.part_id != id.part_id) ||
(slave->id.class_id != id.class_id))
return -ENODEV;
- return 0;
+}
+/* called with bus_lock held */ +static int sdw_get_device_num(struct sdw_slave *slave) +{
- int bit;
- bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES);
- if (bit == SDW_MAX_DEVICES) {
bit = -ENODEV;
goto err;
My brain is starting to fry but is this correct? Bit11 seems like a valid value. Should it be bit > 15 (assuming bit 12,13,14 are set to avoid using groups and master)?
- }
- /*
* Do not update dev_num in Slave data structure here,
* Update once program dev_num is successful
*/
- set_bit(bit, slave->bus->assigned);
+err:
- return bit;
+}
+static int sdw_assign_device_num(struct sdw_slave *slave) +{
- int ret, dev_num;
- /* check first if device number is assigned, if so reuse that */
- if (!slave->dev_num) {
mutex_lock(&slave->bus->bus_lock);
dev_num = sdw_get_device_num(slave);
mutex_unlock(&slave->bus->bus_lock);
if (dev_num < 0) {
dev_err(slave->bus->dev, "Get dev_num failed: %d",
dev_num);
return dev_num;
}
- } else {
dev_info(slave->bus->dev,
"Slave already registered dev_num:%d",
slave->dev_num);
/* Clear the slave->dev_num to transfer message on device 0 */
dev_num = slave->dev_num;
slave->dev_num = 0;
- }
- ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num);
- if (ret < 0) {
dev_err(&slave->dev, "Program device_num failed: %d", ret);
return ret;
- }
- /* After xfer of msg, restore dev_num */
- slave->dev_num = dev_num;
- return 0;
+}
- void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id) {
@@ -400,3 +487,118 @@ void sdw_extract_slave_id(struct sdw_bus *bus, id->unique_id, id->sdw_version);
}
+static int sdw_program_device_num(struct sdw_bus *bus) +{
- u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0};
- struct sdw_slave *slave, *_s;
- struct sdw_slave_id id;
- struct sdw_msg msg;
- bool found = false;
- int count = 0, ret;
- u64 addr;
- /* No Slave, so use raw xfer api */
- ret = sdw_fill_msg(&msg, NULL, SDW_SCP_DEVID_0,
SDW_NUM_DEV_ID_REGISTERS, 0, SDW_MSG_FLAG_READ, buf);
- if (ret < 0)
return ret;
- do {
ret = sdw_transfer(bus, NULL, &msg);
if (ret == -ENODATA) { /* end of device id reads */
ret = 0;
break;
}
if (ret < 0) {
dev_err(bus->dev, "DEVID read fail:%d\n", ret);
break;
}
/*
* Construct the addr and extract. Cast the higher shift
* bits to avoid truncation due to size limit.
*/
addr = buf[5] | (buf[4] << 8) | (buf[3] << 16) |
(buf[2] << 24) | ((unsigned long long)buf[1] << 32) |
((unsigned long long)buf[0] << 40);
sdw_extract_slave_id(bus, addr, &id);
/* Now compare with entries */
list_for_each_entry_safe(slave, _s, &bus->slaves, node) {
if (sdw_compare_devid(slave, id) == 0) {
found = true;
/*
* Assign a new dev_num to this Slave and
* not mark it present. It will be marked
* present after it reports ATTACHED on new
* dev_num
*/
ret = sdw_assign_device_num(slave);
if (ret) {
dev_err(slave->bus->dev,
"Assign dev_num failed:%d",
ret);
return ret;
}
break;
}
}
if (found == false) {
/* TODO: Park this device in Group 13 */
dev_err(bus->dev, "Slave Entry not found");
}
count++;
- } while (ret == 0 && count < (SDW_MAX_DEVICES * 2));
explain that the last condition is intentional - this is not a bug -, some devices can drop off during enumeration and rejoin so might be counted twice.
- return ret;
+}
+static void sdw_modify_slave_status(struct sdw_slave *slave,
enum sdw_slave_status status)
+{
- mutex_lock(&slave->bus->bus_lock);
- slave->status = status;
- mutex_unlock(&slave->bus->bus_lock);
+}
+static int sdw_initialize_slave(struct sdw_slave *slave) +{
- struct sdw_slave_prop *prop = &slave->prop;
- int ret;
- u8 val;
- /* Set bus clash and parity interrupt mask */
- val = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY;
- /* Enable SCP interrupts */
- ret = sdw_update(slave, SDW_SCP_INTMASK1, val, val);
- if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INTMASK1 write failed:%d", ret);
return ret;
- }
- /* No need to continue if DP0 is not present */
- if (!slave->prop.dp0_prop)
return 0;
- /* Enable DP0 interrupts */
- val = prop->dp0_prop->device_interrupts;
- val |= SDW_DP0_INT_PORT_READY | SDW_DP0_INT_BRA_FAILURE;
- ret = sdw_update(slave, SDW_DP0_INTMASK, val, val);
- if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INTMASK read failed:%d", ret);
return val;
- }
- return 0;
+} diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index bde5f840ca96..b491e86f2c4c 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -53,4 +53,18 @@ int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_slave *slave, int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf);
+/* Read-Modify-Write Slave register */ +static inline int +sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) +{
- int tmp;
- tmp = sdw_read(slave, addr);
- if (tmp < 0)
return tmp;
- tmp = (tmp & ~mask) | val;
- return sdw_write(slave, addr, tmp);
+}
- #endif /* __SDW_BUS_H */
diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index d3337b5b882b..09619d042909 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -15,6 +15,7 @@ struct sdw_slave; /* SDW Enumeration Device Number */ #define SDW_ENUM_DEV_NUM 0
+#define SDW_NUM_DEV_ID_REGISTERS 6 #define SDW_MAX_DEVICES 11
/**
On Fri, Dec 01, 2017 at 05:36:47PM -0600, Pierre-Louis Bossart wrote:
+/* called with bus_lock held */ +static int sdw_get_device_num(struct sdw_slave *slave) +{
- int bit;
- bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES);
- if (bit == SDW_MAX_DEVICES) {
bit = -ENODEV;
goto err;
My brain is starting to fry but is this correct? Bit11 seems like a valid value. Should it be bit > 15 (assuming bit 12,13,14 are set to avoid using groups and master)?
this is correct. You are confusing SDW concept and API return types! That should be hint for you to start weekend if you didn't do so :D
This API returns max value it was provided (last arg) if it doesn't find free bit. That's an indication to caller that we ran out of devices hence ENODEV error!
+static int sdw_program_device_num(struct sdw_bus *bus) +{
- u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0};
- struct sdw_slave *slave, *_s;
- struct sdw_slave_id id;
- struct sdw_msg msg;
- bool found = false;
- int count = 0, ret;
- u64 addr;
- /* No Slave, so use raw xfer api */
- ret = sdw_fill_msg(&msg, NULL, SDW_SCP_DEVID_0,
SDW_NUM_DEV_ID_REGISTERS, 0, SDW_MSG_FLAG_READ, buf);
- if (ret < 0)
return ret;
- do {
ret = sdw_transfer(bus, NULL, &msg);
if (ret == -ENODATA) { /* end of device id reads */
ret = 0;
break;
}
if (ret < 0) {
dev_err(bus->dev, "DEVID read fail:%d\n", ret);
break;
}
/*
* Construct the addr and extract. Cast the higher shift
* bits to avoid truncation due to size limit.
*/
addr = buf[5] | (buf[4] << 8) | (buf[3] << 16) |
(buf[2] << 24) | ((unsigned long long)buf[1] << 32) |
((unsigned long long)buf[0] << 40);
sdw_extract_slave_id(bus, addr, &id);
/* Now compare with entries */
list_for_each_entry_safe(slave, _s, &bus->slaves, node) {
if (sdw_compare_devid(slave, id) == 0) {
found = true;
/*
* Assign a new dev_num to this Slave and
* not mark it present. It will be marked
* present after it reports ATTACHED on new
* dev_num
*/
ret = sdw_assign_device_num(slave);
if (ret) {
dev_err(slave->bus->dev,
"Assign dev_num failed:%d",
ret);
return ret;
}
break;
}
}
if (found == false) {
/* TODO: Park this device in Group 13 */
dev_err(bus->dev, "Slave Entry not found");
}
count++;
- } while (ret == 0 && count < (SDW_MAX_DEVICES * 2));
explain that the last condition is intentional - this is not a bug -, some devices can drop off during enumeration and rejoin so might be counted twice.
ok will add
On 12/3/17 11:08 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:36:47PM -0600, Pierre-Louis Bossart wrote:
+/* called with bus_lock held */ +static int sdw_get_device_num(struct sdw_slave *slave) +{
- int bit;
- bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES);
- if (bit == SDW_MAX_DEVICES) {
bit = -ENODEV;
goto err;
My brain is starting to fry but is this correct? Bit11 seems like a valid value. Should it be bit > 15 (assuming bit 12,13,14 are set to avoid using groups and master)?
this is correct. You are confusing SDW concept and API return types! That should be hint for you to start weekend if you didn't do so :D
This API returns max value it was provided (last arg) if it doesn't find free bit. That's an indication to caller that we ran out of devices hence ENODEV error!
Can you just make sure bit11 is included?
+static int sdw_program_device_num(struct sdw_bus *bus) +{
- u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0};
- struct sdw_slave *slave, *_s;
- struct sdw_slave_id id;
- struct sdw_msg msg;
- bool found = false;
- int count = 0, ret;
- u64 addr;
- /* No Slave, so use raw xfer api */
- ret = sdw_fill_msg(&msg, NULL, SDW_SCP_DEVID_0,
SDW_NUM_DEV_ID_REGISTERS, 0, SDW_MSG_FLAG_READ, buf);
- if (ret < 0)
return ret;
- do {
ret = sdw_transfer(bus, NULL, &msg);
if (ret == -ENODATA) { /* end of device id reads */
ret = 0;
break;
}
if (ret < 0) {
dev_err(bus->dev, "DEVID read fail:%d\n", ret);
break;
}
/*
* Construct the addr and extract. Cast the higher shift
* bits to avoid truncation due to size limit.
*/
addr = buf[5] | (buf[4] << 8) | (buf[3] << 16) |
(buf[2] << 24) | ((unsigned long long)buf[1] << 32) |
((unsigned long long)buf[0] << 40);
sdw_extract_slave_id(bus, addr, &id);
/* Now compare with entries */
list_for_each_entry_safe(slave, _s, &bus->slaves, node) {
if (sdw_compare_devid(slave, id) == 0) {
found = true;
/*
* Assign a new dev_num to this Slave and
* not mark it present. It will be marked
* present after it reports ATTACHED on new
* dev_num
*/
ret = sdw_assign_device_num(slave);
if (ret) {
dev_err(slave->bus->dev,
"Assign dev_num failed:%d",
ret);
return ret;
}
break;
}
}
if (found == false) {
/* TODO: Park this device in Group 13 */
dev_err(bus->dev, "Slave Entry not found");
}
count++;
- } while (ret == 0 && count < (SDW_MAX_DEVICES * 2));
explain that the last condition is intentional - this is not a bug -, some devices can drop off during enumeration and rejoin so might be counted twice.
ok will add
On Sun, Dec 03, 2017 at 09:07:29PM -0600, Pierre-Louis Bossart wrote:
On 12/3/17 11:08 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:36:47PM -0600, Pierre-Louis Bossart wrote:
+/* called with bus_lock held */ +static int sdw_get_device_num(struct sdw_slave *slave) +{
- int bit;
- bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES);
- if (bit == SDW_MAX_DEVICES) {
bit = -ENODEV;
goto err;
My brain is starting to fry but is this correct? Bit11 seems like a valid value. Should it be bit > 15 (assuming bit 12,13,14 are set to avoid using groups and master)?
this is correct. You are confusing SDW concept and API return types! That should be hint for you to start weekend if you didn't do so :D
This API returns max value it was provided (last arg) if it doesn't find free bit. That's an indication to caller that we ran out of devices hence ENODEV error!
Can you just make sure bit11 is included?
yes it is, refer to the masks we set for bit, only 0 and 15 and now 12,13 and 14 will be masked out. So we can get from 1 to 11 both inclusive.
Add status handling API sdw_handle_slave_status() to handle Slave status changes.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/bus.c | 351 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 2 + include/linux/soundwire/sdw.h | 20 +++ 3 files changed, 373 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 09126ddd3cdd..c6a59a7a1306 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -602,3 +602,354 @@ static int sdw_initialize_slave(struct sdw_slave *slave)
return 0; } + +static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status) +{ + u8 clear = 0, impl_int_mask; + int status, ret, count = 0; + + status = sdw_read(slave, SDW_DP0_INT); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT read failed:%d", status); + return status; + } + + do { + + if (status & SDW_DP0_INT_TEST_FAIL) { + dev_err(&slave->dev, "Test fail for port 0"); + clear |= SDW_DP0_INT_TEST_FAIL; + } + + /* + * Assumption: PORT_READY interrupt will be received only for + * ports implementing Channel Prepare state machine (CP_SM) + */ + + if (status & SDW_DP0_INT_PORT_READY) { + complete(&slave->port_ready[0]); + clear |= SDW_DP0_INT_PORT_READY; + } + + if (status & SDW_DP0_INT_BRA_FAILURE) { + dev_err(&slave->dev, "BRA failed"); + clear |= SDW_DP0_INT_BRA_FAILURE; + } + + impl_int_mask = SDW_DP0_INT_IMPDEF1 | + SDW_DP0_INT_IMPDEF2 | SDW_DP0_INT_IMPDEF3; + + if (status & impl_int_mask) { + clear |= impl_int_mask; + *slave_status = clear; + } + + /* clear the interrupt */ + ret = sdw_write(slave, SDW_DP0_INT, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT write failed:%d", ret); + return ret; + } + + /* Read DP0 interrupt again */ + status = sdw_read(slave, SDW_DP0_INT); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT read failed:%d", status); + return status; + } + + count++; + + /* we can get alerts while processing so keep retrying */ + } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on DP0 read"); + + return ret; +} + +static int sdw_handle_port_interrupt(struct sdw_slave *slave, + int port, u8 *slave_status) +{ + u8 clear = 0, impl_int_mask; + int status, ret, count = 0; + u32 addr; + + if (port == 0) + return sdw_handle_dp0_interrupt(slave, slave_status); + + addr = SDW_DPN_INT(port); + status = sdw_read(slave, addr); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT read failed:%d", status); + + return status; + } + + do { + + if (status & SDW_DPN_INT_TEST_FAIL) { + dev_err(&slave->dev, "Test fail for port:%d", port); + clear |= SDW_DPN_INT_TEST_FAIL; + } + + /* + * Assumption: PORT_READY interrupt will be received only + * for ports implementing CP_SM. + */ + if (status & SDW_DPN_INT_PORT_READY) { + complete(&slave->port_ready[port]); + clear |= SDW_DPN_INT_PORT_READY; + } + + impl_int_mask = SDW_DPN_INT_IMPDEF1 | + SDW_DPN_INT_IMPDEF2 | SDW_DPN_INT_IMPDEF3; + + + if (status & impl_int_mask) { + clear |= impl_int_mask; + *slave_status = clear; + } + + /* clear the interrupt */ + ret = sdw_write(slave, addr, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT write failed:%d", ret); + return ret; + } + + /* Read DPN interrupt again */ + status = sdw_read(slave, addr); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT read failed:%d", status); + return status; + } + + count++; + + /* we can get alerts while processing so keep retrying */ + } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on port read"); + + return ret; +} + +static int sdw_handle_slave_alerts(struct sdw_slave *slave) +{ + u8 clear = 0, bit, port_status[15]; + int port_num, stat, ret, count = 0; + unsigned long port; + bool slave_notify = false; + u8 buf[3]; + + sdw_modify_slave_status(slave, SDW_SLAVE_ALERT); + + /* Read Instat 1, Instat 2 and Instat 3 registers */ + ret = sdw_nread(slave, SDW_SCP_INT1, 3, buf); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 read failed:%d", ret); + return ret; + } + + do { + /* + * Check parity, bus clash and Slave (impl defined) + * interrupt + */ + if (buf[0] & SDW_SCP_INT1_PARITY) { + dev_err(&slave->dev, "Parity error detected"); + clear |= SDW_SCP_INT1_PARITY; + } + + if (buf[0] & SDW_SCP_INT1_BUS_CLASH) { + dev_err(&slave->dev, "Bus clash error detected"); + clear |= SDW_SCP_INT1_BUS_CLASH; + } + + /* + * When bus clash or parity errors are detected, such errors + * are unlikely to be recoverable errors. + * TODO: In such scenario, reset bus. Make this configurable + * via sysfs property with bus reset being the default. + */ + + if (buf[0] & SDW_SCP_INT1_IMPL_DEF) { + dev_dbg(&slave->dev, "Slave impl defined interrupt\n"); + clear |= SDW_SCP_INT1_IMPL_DEF; + slave_notify = true; + } + + /* Check port 0 - 4 interrupts */ + port = buf[0] & SDW_SCP_INT1_PORT0_3; + + /* To get port number corresponding to bits, shift it */ + port = port >> SDW_REG_SHIFT(SDW_SCP_INT1_PORT0_3); + for_each_set_bit(bit, &port, 8) { + sdw_handle_port_interrupt(slave, bit, + &port_status[bit]); + + } + + /* Check if cascade 2 interrupt is present */ + if (buf[0] & SDW_SCP_INT1_SCP2_CASCADE) { + port = buf[1] & SDW_SCP_INTSTAT2_PORT4_10; + for_each_set_bit(bit, &port, 8) { + /* scp2 ports start from 4 */ + port_num = bit + 3; + sdw_handle_port_interrupt(slave, + port_num, + &port_status[port_num]); + } + } + + /* now check last cascade */ + if (buf[1] & SDW_SCP_INTSTAT2_SCP3_CASCADE) { + port = buf[2] & SDW_SCP_INTSTAT3_PORT11_14; + for_each_set_bit(bit, &port, 8) { + /* scp3 ports start from 11 */ + port_num = bit + 10; + sdw_handle_port_interrupt(slave, + port_num, + &port_status[port_num]); + } + } + + /* Update the Slave driver */ + if (slave_notify && (slave->ops) && + (slave->ops->interrupt_callback)) { + struct sdw_slave_intr_status slave_intr; + + slave_intr.control_port = clear; + memcpy(slave_intr.port, &port_status, + sizeof(slave_intr.port)); + + slave->ops->interrupt_callback(slave, &slave_intr); + } + + /* Ack interrupt */ + ret = sdw_write(slave, SDW_SCP_INT1, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 write failed:%d", ret); + return ret; + } + + /* + * Read status again to ensure no new interrupts arrived + * while servicing interrupts + */ + ret = sdw_nread(slave, SDW_SCP_INT1, 3, buf); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 read failed:%d", ret); + return ret; + } + + /* Make sure no interrupts are pending */ + stat = buf[0] || buf[1] || buf[2]; + + /* + * Exit loop if Slave is continuously in ALERT state even + * after servicing the interrupt multiple times. + */ + count++; + + /* we can get alerts while processing so keep retrying */ + } while (stat != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on alert read"); + + return ret; +} + +static int sdw_update_slave_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + if ((slave->ops) && (slave->ops->update_status)) + return slave->ops->update_status(slave, status); + + return 0; +} + +/** + * sdw_handle_slave_status() - Handle Slave status + * @bus: SDW bus instance + * @status: Status for all Slave(s) + */ +int sdw_handle_slave_status(struct sdw_bus *bus, + enum sdw_slave_status status[]) +{ + struct sdw_slave *slave; + int i, ret = 0; + + if (status[0] == SDW_SLAVE_ATTACHED) { + ret = sdw_program_device_num(bus); + if (ret) + dev_err(bus->dev, "Slave attach failed: %d", ret); + } + + /* Continue to check other slave statuses */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + switch (status[i]) { + case SDW_SLAVE_UNATTACHED: + if (slave->status == SDW_SLAVE_UNATTACHED) + break; + + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + break; + + case SDW_SLAVE_ALERT: + ret = sdw_handle_slave_alerts(slave); + if (ret) + dev_err(bus->dev, + "Slave %d alert handling failed: %d", + i, ret); + break; + + case SDW_SLAVE_ATTACHED: + if (slave->status == SDW_SLAVE_ATTACHED) + break; + + sdw_initialize_slave(slave); + sdw_modify_slave_status(slave, SDW_SLAVE_ATTACHED); + + break; + + default: + dev_err(bus->dev, "Invalid slave %d status:%d", + i, status[i]); + break; + } + + ret = sdw_update_slave_status(slave, status[i]); + if (ret) + dev_err(slave->bus->dev, + "Update Slave status failed:%d", ret); + + } + + return ret; +} +EXPORT_SYMBOL(sdw_handle_slave_status); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index b491e86f2c4c..463fecb02563 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -45,6 +45,8 @@ struct sdw_msg { bool page; };
+#define SDW_READ_INTR_CLEAR_RETRY 10 + int sdw_transfer(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_msg *msg); int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_slave *slave, diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 09619d042909..861a910911b6 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -323,11 +323,28 @@ struct sdw_slave_id { };
/** + * struct sdw_slave_intr_status - Slave interrupt status + * @control_port: control port status + * @port: data port status + */ +struct sdw_slave_intr_status { + u8 control_port; + u8 port[14]; +}; + +/** * struct sdw_slave_ops - Slave driver callback ops * @read_prop: Read Slave properties + * @interrupt_callback: Device interrupt notification (invoked in thread + * context) + * @update_status: Update Slave status */ struct sdw_slave_ops { int (*read_prop)(struct sdw_slave *sdw); + int (*interrupt_callback)(struct sdw_slave *slave, + struct sdw_slave_intr_status *status); + int (*update_status)(struct sdw_slave *slave, + enum sdw_slave_status status); };
/** @@ -377,6 +394,9 @@ struct sdw_driver { int sdw_handle_slave_status(struct sdw_bus *bus, enum sdw_slave_status status[]);
+int sdw_handle_slave_status(struct sdw_bus *bus, + enum sdw_slave_status status[]); + /* * SDW master structures and APIs */
On 12/1/17 3:56 AM, Vinod Koul wrote:
Add status handling API sdw_handle_slave_status() to handle Slave status changes.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/bus.c | 351 ++++++++++++++++++++++++++++++++++++++++++ drivers/soundwire/bus.h | 2 + include/linux/soundwire/sdw.h | 20 +++ 3 files changed, 373 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 09126ddd3cdd..c6a59a7a1306 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -602,3 +602,354 @@ static int sdw_initialize_slave(struct sdw_slave *slave)
return 0; }
+static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status) +{
- u8 clear = 0, impl_int_mask;
- int status, ret, count = 0;
- status = sdw_read(slave, SDW_DP0_INT);
- if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INT read failed:%d", status);
return status;
- }
- do {
if (status & SDW_DP0_INT_TEST_FAIL) {
dev_err(&slave->dev, "Test fail for port 0");
clear |= SDW_DP0_INT_TEST_FAIL;
}
/*
* Assumption: PORT_READY interrupt will be received only for
* ports implementing Channel Prepare state machine (CP_SM)
*/
if (status & SDW_DP0_INT_PORT_READY) {
complete(&slave->port_ready[0]);
clear |= SDW_DP0_INT_PORT_READY;
}
if (status & SDW_DP0_INT_BRA_FAILURE) {
dev_err(&slave->dev, "BRA failed");
clear |= SDW_DP0_INT_BRA_FAILURE;
}
impl_int_mask = SDW_DP0_INT_IMPDEF1 |
SDW_DP0_INT_IMPDEF2 | SDW_DP0_INT_IMPDEF3;
if (status & impl_int_mask) {
clear |= impl_int_mask;
*slave_status = clear;
}
/* clear the interrupt */
ret = sdw_write(slave, SDW_DP0_INT, clear);
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INT write failed:%d", ret);
return ret;
}
/* Read DP0 interrupt again */
status = sdw_read(slave, SDW_DP0_INT);
if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INT read failed:%d", status);
return status;
}
count++;
/* we can get alerts while processing so keep retrying */
This is not incorrect, but this goes beyond what the spec requires.
The additional read is to make sure some interrupts are not lost due to a known race condition. It would be enough to mask the status read the second time to only check if the interrupts sources which were cleared are still signaling something.
With the code as it is, you may catch *new* interrupt sources, which could impact the arbitration/priority/policy in handling interrupts. It's not necessarily bad, but you'd need to document whether you want to deal with the race condition described in the MIPI spec or try to be smarter.
- } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
- if (count == SDW_READ_INTR_CLEAR_RETRY)
dev_warn(slave->bus->dev, "Reached MAX_RETRY on DP0 read");
- return ret;
+}
+static int sdw_handle_port_interrupt(struct sdw_slave *slave,
int port, u8 *slave_status)
+{
- u8 clear = 0, impl_int_mask;
- int status, ret, count = 0;
- u32 addr;
- if (port == 0)
return sdw_handle_dp0_interrupt(slave, slave_status);
- addr = SDW_DPN_INT(port);
- status = sdw_read(slave, addr);
- if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DPN_INT read failed:%d", status);
return status;
- }
- do {
if (status & SDW_DPN_INT_TEST_FAIL) {
dev_err(&slave->dev, "Test fail for port:%d", port);
clear |= SDW_DPN_INT_TEST_FAIL;
}
/*
* Assumption: PORT_READY interrupt will be received only
* for ports implementing CP_SM.
*/
if (status & SDW_DPN_INT_PORT_READY) {
complete(&slave->port_ready[port]);
clear |= SDW_DPN_INT_PORT_READY;
}
impl_int_mask = SDW_DPN_INT_IMPDEF1 |
SDW_DPN_INT_IMPDEF2 | SDW_DPN_INT_IMPDEF3;
if (status & impl_int_mask) {
clear |= impl_int_mask;
*slave_status = clear;
}
/* clear the interrupt */
ret = sdw_write(slave, addr, clear);
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_DPN_INT write failed:%d", ret);
return ret;
}
/* Read DPN interrupt again */
status = sdw_read(slave, addr);
if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DPN_INT read failed:%d", status);
return status;
}
count++;
/* we can get alerts while processing so keep retrying */
- } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
- if (count == SDW_READ_INTR_CLEAR_RETRY)
dev_warn(slave->bus->dev, "Reached MAX_RETRY on port read");
- return ret;
+}
+static int sdw_handle_slave_alerts(struct sdw_slave *slave) +{
- u8 clear = 0, bit, port_status[15];
- int port_num, stat, ret, count = 0;
- unsigned long port;
- bool slave_notify = false;
- u8 buf[3];
- sdw_modify_slave_status(slave, SDW_SLAVE_ALERT);
- /* Read Instat 1, Instat 2 and Instat 3 registers */
- ret = sdw_nread(slave, SDW_SCP_INT1, 3, buf);
- if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INT1 read failed:%d", ret);
return ret;
- }
- do {
/*
* Check parity, bus clash and Slave (impl defined)
* interrupt
*/
if (buf[0] & SDW_SCP_INT1_PARITY) {
dev_err(&slave->dev, "Parity error detected");
clear |= SDW_SCP_INT1_PARITY;
}
if (buf[0] & SDW_SCP_INT1_BUS_CLASH) {
dev_err(&slave->dev, "Bus clash error detected");
clear |= SDW_SCP_INT1_BUS_CLASH;
}
/*
* When bus clash or parity errors are detected, such errors
* are unlikely to be recoverable errors.
* TODO: In such scenario, reset bus. Make this configurable
* via sysfs property with bus reset being the default.
*/
if (buf[0] & SDW_SCP_INT1_IMPL_DEF) {
dev_dbg(&slave->dev, "Slave impl defined interrupt\n");
clear |= SDW_SCP_INT1_IMPL_DEF;
slave_notify = true;
}
/* Check port 0 - 4 interrupts */
port = buf[0] & SDW_SCP_INT1_PORT0_3;
/* To get port number corresponding to bits, shift it */
port = port >> SDW_REG_SHIFT(SDW_SCP_INT1_PORT0_3);
for_each_set_bit(bit, &port, 8) {
sdw_handle_port_interrupt(slave, bit,
&port_status[bit]);
}
/* Check if cascade 2 interrupt is present */
if (buf[0] & SDW_SCP_INT1_SCP2_CASCADE) {
port = buf[1] & SDW_SCP_INTSTAT2_PORT4_10;
for_each_set_bit(bit, &port, 8) {
/* scp2 ports start from 4 */
port_num = bit + 3;
sdw_handle_port_interrupt(slave,
port_num,
&port_status[port_num]);
}
}
/* now check last cascade */
if (buf[1] & SDW_SCP_INTSTAT2_SCP3_CASCADE) {
port = buf[2] & SDW_SCP_INTSTAT3_PORT11_14;
for_each_set_bit(bit, &port, 8) {
/* scp3 ports start from 11 */
port_num = bit + 10;
sdw_handle_port_interrupt(slave,
port_num,
&port_status[port_num]);
}
}
/* Update the Slave driver */
if (slave_notify && (slave->ops) &&
(slave->ops->interrupt_callback)) {
struct sdw_slave_intr_status slave_intr;
slave_intr.control_port = clear;
memcpy(slave_intr.port, &port_status,
sizeof(slave_intr.port));
slave->ops->interrupt_callback(slave, &slave_intr);
}
/* Ack interrupt */
ret = sdw_write(slave, SDW_SCP_INT1, clear);
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INT1 write failed:%d", ret);
return ret;
}
/*
* Read status again to ensure no new interrupts arrived
* while servicing interrupts
*/
ret = sdw_nread(slave, SDW_SCP_INT1, 3, buf);
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INT1 read failed:%d", ret);
return ret;
}
/* Make sure no interrupts are pending */
stat = buf[0] || buf[1] || buf[2];
/*
* Exit loop if Slave is continuously in ALERT state even
* after servicing the interrupt multiple times.
*/
count++;
/* we can get alerts while processing so keep retrying */
- } while (stat != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
so that's a different case from what is done above, here you are making a conscious decision to re-read the interrupt status, it's got nothing to do with the spec requirements and you'd probably want a different RETRY value.
- if (count == SDW_READ_INTR_CLEAR_RETRY)
dev_warn(slave->bus->dev, "Reached MAX_RETRY on alert read");
- return ret;
+}
+static int sdw_update_slave_status(struct sdw_slave *slave,
enum sdw_slave_status status)
+{
- if ((slave->ops) && (slave->ops->update_status))
return slave->ops->update_status(slave, status);
- return 0;
+}
+/**
- sdw_handle_slave_status() - Handle Slave status
- @bus: SDW bus instance
- @status: Status for all Slave(s)
- */
+int sdw_handle_slave_status(struct sdw_bus *bus,
enum sdw_slave_status status[])
+{
- struct sdw_slave *slave;
- int i, ret = 0;
- if (status[0] == SDW_SLAVE_ATTACHED) {
ret = sdw_program_device_num(bus);
if (ret)
dev_err(bus->dev, "Slave attach failed: %d", ret);
- }
- /* Continue to check other slave statuses */
- for (i = 1; i <= SDW_MAX_DEVICES; i++) {
mutex_lock(&bus->bus_lock);
if (test_bit(i, bus->assigned) == false) {
mutex_unlock(&bus->bus_lock);
continue;
}
mutex_unlock(&bus->bus_lock);
slave = sdw_get_slave(bus, i);
if (!slave)
continue;
switch (status[i]) {
case SDW_SLAVE_UNATTACHED:
if (slave->status == SDW_SLAVE_UNATTACHED)
break;
sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
break;
case SDW_SLAVE_ALERT:
ret = sdw_handle_slave_alerts(slave);
if (ret)
dev_err(bus->dev,
"Slave %d alert handling failed: %d",
i, ret);
break;
case SDW_SLAVE_ATTACHED:
if (slave->status == SDW_SLAVE_ATTACHED)
break;
sdw_initialize_slave(slave);
sdw_modify_slave_status(slave, SDW_SLAVE_ATTACHED);
break;
default:
dev_err(bus->dev, "Invalid slave %d status:%d",
i, status[i]);
break;
}
ret = sdw_update_slave_status(slave, status[i]);
if (ret)
dev_err(slave->bus->dev,
"Update Slave status failed:%d", ret);
- }
- return ret;
+} +EXPORT_SYMBOL(sdw_handle_slave_status); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index b491e86f2c4c..463fecb02563 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -45,6 +45,8 @@ struct sdw_msg { bool page; };
+#define SDW_READ_INTR_CLEAR_RETRY 10
- int sdw_transfer(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_msg *msg); int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_slave *slave,
diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 09619d042909..861a910911b6 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -323,11 +323,28 @@ struct sdw_slave_id { };
/**
- struct sdw_slave_intr_status - Slave interrupt status
- @control_port: control port status
- @port: data port status
- */
+struct sdw_slave_intr_status {
- u8 control_port;
- u8 port[14];
+};
+/**
- struct sdw_slave_ops - Slave driver callback ops
- @read_prop: Read Slave properties
- @interrupt_callback: Device interrupt notification (invoked in thread
- context)
- @update_status: Update Slave status
*/ struct sdw_slave_ops { int (*read_prop)(struct sdw_slave *sdw);
int (*interrupt_callback)(struct sdw_slave *slave,
struct sdw_slave_intr_status *status);
int (*update_status)(struct sdw_slave *slave,
enum sdw_slave_status status);
};
/**
@@ -377,6 +394,9 @@ struct sdw_driver { int sdw_handle_slave_status(struct sdw_bus *bus, enum sdw_slave_status status[]);
+int sdw_handle_slave_status(struct sdw_bus *bus,
enum sdw_slave_status status[]);
- /*
*/
- SDW master structures and APIs
On Fri, Dec 01, 2017 at 05:52:03PM -0600, Pierre-Louis Bossart wrote:
status = sdw_read(slave, SDW_DP0_INT);
if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INT read failed:%d", status);
return status;
}
count++;
/* we can get alerts while processing so keep retrying */
This is not incorrect, but this goes beyond what the spec requires.
The additional read is to make sure some interrupts are not lost due to a known race condition. It would be enough to mask the status read the second time to only check if the interrupts sources which were cleared are still signaling something.
With the code as it is, you may catch *new* interrupt sources, which could impact the arbitration/priority/policy in handling interrupts. It's not necessarily bad, but you'd need to document whether you want to deal with the race condition described in the MIPI spec or try to be smarter.
This was based on your last comment, lets discuss more offline on this to see what else is required here.
On 12/3/17 11:11 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:52:03PM -0600, Pierre-Louis Bossart wrote:
status = sdw_read(slave, SDW_DP0_INT);
if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INT read failed:%d", status);
return status;
}
count++;
/* we can get alerts while processing so keep retrying */
This is not incorrect, but this goes beyond what the spec requires.
The additional read is to make sure some interrupts are not lost due to a known race condition. It would be enough to mask the status read the second time to only check if the interrupts sources which were cleared are still signaling something.
With the code as it is, you may catch *new* interrupt sources, which could impact the arbitration/priority/policy in handling interrupts. It's not necessarily bad, but you'd need to document whether you want to deal with the race condition described in the MIPI spec or try to be smarter.
This was based on your last comment, lets discuss more offline on this to see what else is required here.
I am fine if you leave the code as is for now, it's not bad but can be optimized.
On Sun, Dec 03, 2017 at 09:11:39PM -0600, Pierre-Louis Bossart wrote:
On 12/3/17 11:11 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:52:03PM -0600, Pierre-Louis Bossart wrote:
status = sdw_read(slave, SDW_DP0_INT);
if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INT read failed:%d", status);
return status;
}
count++;
/* we can get alerts while processing so keep retrying */
This is not incorrect, but this goes beyond what the spec requires.
The additional read is to make sure some interrupts are not lost due to a known race condition. It would be enough to mask the status read the second time to only check if the interrupts sources which were cleared are still signaling something.
With the code as it is, you may catch *new* interrupt sources, which could impact the arbitration/priority/policy in handling interrupts. It's not necessarily bad, but you'd need to document whether you want to deal with the race condition described in the MIPI spec or try to be smarter.
This was based on your last comment, lets discuss more offline on this to see what else is required here.
I am fine if you leave the code as is for now, it's not bad but can be optimized.
Not bad is not good here :)
Okay I still havent grabbed my coffee, so help me out here. I am not sure I understand here, can you point me to the part of spec handling you were referring and what should be *ideally* done
On 12/3/17 9:21 PM, Vinod Koul wrote:
On Sun, Dec 03, 2017 at 09:11:39PM -0600, Pierre-Louis Bossart wrote:
On 12/3/17 11:11 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:52:03PM -0600, Pierre-Louis Bossart wrote:
status = sdw_read(slave, SDW_DP0_INT);
if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INT read failed:%d", status);
return status;
}
count++;
/* we can get alerts while processing so keep retrying */
This is not incorrect, but this goes beyond what the spec requires.
The additional read is to make sure some interrupts are not lost due to a known race condition. It would be enough to mask the status read the second time to only check if the interrupts sources which were cleared are still signaling something.
With the code as it is, you may catch *new* interrupt sources, which could impact the arbitration/priority/policy in handling interrupts. It's not necessarily bad, but you'd need to document whether you want to deal with the race condition described in the MIPI spec or try to be smarter.
This was based on your last comment, lets discuss more offline on this to see what else is required here.
I am fine if you leave the code as is for now, it's not bad but can be optimized.
Not bad is not good here :)
Okay I still havent grabbed my coffee, so help me out here. I am not sure I understand here, can you point me to the part of spec handling you were referring and what should be *ideally* done
You first read the status, then clear the interrupts then re-read the status. I'd be good enough in the second read to mask with the settings of the first read. This is intended to detect alert sources that fired between the last successful read and the write to clear interrupts (see Figure 92 in the 1.1 spec)
e.g.
do { status1= sdw_read() deal with interrupts status2 = sdw_read() status2 &= status1; /* filter initial sources */
/* we can get alerts while processing so keep retrying */ } while (status2 != 0 && count < SDW_READ_INTR_CLEAR_RETRY);
On Sun, Dec 03, 2017 at 09:52:48PM -0600, Pierre-Louis Bossart wrote:
On 12/3/17 9:21 PM, Vinod Koul wrote:
On Sun, Dec 03, 2017 at 09:11:39PM -0600, Pierre-Louis Bossart wrote:
On 12/3/17 11:11 AM, Vinod Koul wrote:
On Fri, Dec 01, 2017 at 05:52:03PM -0600, Pierre-Louis Bossart wrote:
status = sdw_read(slave, SDW_DP0_INT);
if (status < 0) {
dev_err(slave->bus->dev,
"SDW_DP0_INT read failed:%d", status);
return status;
}
count++;
/* we can get alerts while processing so keep retrying */
This is not incorrect, but this goes beyond what the spec requires.
The additional read is to make sure some interrupts are not lost due to a known race condition. It would be enough to mask the status read the second time to only check if the interrupts sources which were cleared are still signaling something.
With the code as it is, you may catch *new* interrupt sources, which could impact the arbitration/priority/policy in handling interrupts. It's not necessarily bad, but you'd need to document whether you want to deal with the race condition described in the MIPI spec or try to be smarter.
This was based on your last comment, lets discuss more offline on this to see what else is required here.
I am fine if you leave the code as is for now, it's not bad but can be optimized.
Not bad is not good here :)
Okay I still havent grabbed my coffee, so help me out here. I am not sure I understand here, can you point me to the part of spec handling you were referring and what should be *ideally* done
You first read the status, then clear the interrupts then re-read the status. I'd be good enough in the second read to mask with the settings of the first read. This is intended to detect alert sources that fired between the last successful read and the write to clear interrupts (see Figure 92 in the 1.1 spec)
e.g.
do { status1= sdw_read() deal with interrupts status2 = sdw_read() status2 &= status1; /* filter initial sources */
Sounds better, updated now, thanks
It helps to read the properties for understanding and debugging systems, so add sysfs files for SoundWire DisCo properties.
TODO: Add ABI files for sysfs
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 3 + drivers/soundwire/bus.h | 2 + drivers/soundwire/slave.c | 1 + drivers/soundwire/sysfs.c | 343 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 13 ++ 6 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 drivers/soundwire/sysfs.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index bcde0d26524c..67dc7b546258 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -3,5 +3,5 @@ #
#Bus Objs -soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o +soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o sysfs.o obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index c6a59a7a1306..6f933413d247 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -41,6 +41,8 @@ int sdw_add_bus_master(struct sdw_bus *bus) } }
+ sdw_sysfs_bus_init(bus); + /* * Device numbers in SoundWire are 0 thru 15 with 0 being * Enumeration device number and 15 broadcast device number. So @@ -101,6 +103,7 @@ static int sdw_delete_slave(struct device *dev, void *data) */ void sdw_delete_bus_master(struct sdw_bus *bus) { + sdw_sysfs_bus_exit(bus); device_for_each_child(bus->dev, NULL, sdw_delete_slave); } EXPORT_SYMBOL(sdw_delete_bus_master); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 463fecb02563..282950f4b513 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -16,6 +16,8 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id);
+extern const struct attribute_group *sdw_slave_dev_attr_groups[]; + enum { SDW_MSG_FLAG_READ = 0, SDW_MSG_FLAG_WRITE, diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c index 397e100d66e3..7b2e2e3da0b2 100644 --- a/drivers/soundwire/slave.c +++ b/drivers/soundwire/slave.c @@ -34,6 +34,7 @@ static int sdw_slave_add(struct sdw_bus *bus, id->class_id, id->unique_id);
slave->dev.release = sdw_slave_release; + slave->dev.groups = sdw_slave_dev_attr_groups; slave->dev.bus = &sdw_bus_type; slave->bus = bus; slave->status = SDW_SLAVE_UNATTACHED; diff --git a/drivers/soundwire/sysfs.c b/drivers/soundwire/sysfs.c new file mode 100644 index 000000000000..9e9093c17dcf --- /dev/null +++ b/drivers/soundwire/sysfs.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#include <linux/device.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include "bus.h" + +/* + * The sysfs for properties reflects the MIPI description as given + * in the MIPI DisCo spec + * + * Base file is: + * properties + * |---- interface-revision + * |---- master-count + * |---- link-N + * |---- clock-stop-modes + * |---- max-clock-frequency + * |---- clock-frequencies + * |---- default-frame-rows + * |---- default-frame-cols + * |---- dynamic-frame-shape + * |---- command-error-threshold + */ + +struct sdw_master_sysfs { + struct kobject kobj; + struct sdw_bus *bus; +}; + +struct sdw_prop_attribute { + struct attribute attr; + ssize_t (*show)(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf); +}; + +static ssize_t sdw_prop_attr_show(struct kobject *kobj, + struct attribute *attr, char *buf) +{ + struct sdw_prop_attribute *prop_attr = + container_of(attr, struct sdw_prop_attribute, attr); + struct sdw_master_sysfs *master = + container_of(kobj, struct sdw_master_sysfs, kobj); + + if (!prop_attr->show) + return -EIO; + + return prop_attr->show(master->bus, prop_attr, buf); +} + +static const struct sysfs_ops sdw_prop_sysfs_ops = { + .show = sdw_prop_attr_show, +}; + +static void prop_release(struct kobject *kobj) +{ + struct sdw_master_sysfs *master = + container_of(kobj, struct sdw_master_sysfs, kobj); + + kfree(master); +} + +static struct kobj_type sdw_master_prop_ktype = { + .release = prop_release, + .sysfs_ops = &sdw_prop_sysfs_ops, +}; + + +#define MASTER_ATTR(_name) \ + struct sdw_prop_attribute master_attr_##_name = __ATTR_RO(_name) + +static ssize_t revision_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", bus->prop.revision); +} + +static ssize_t count_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", bus->prop.master_count); +} + +static ssize_t clock_stop_modes_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", bus->prop.clk_stop_mode); +} + +static ssize_t max_clock_frequency_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", bus->prop.max_freq); +} + +static ssize_t clock_frequencies_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + ssize_t size = 0; + int i; + + for (i = 0; i < bus->prop.num_freq; i++) + size += sprintf(buf + size, "%8d\n", bus->prop.freq[i]); + + return size; +} + +static ssize_t clock_gears_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + ssize_t size = 0; + int i; + + for (i = 0; i < bus->prop.num_clk_gears; i++) + size += sprintf(buf + size, "%8d\n", bus->prop.clk_gears[i]); + + return size; +} + +static ssize_t default_frame_rows_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", bus->prop.default_row); +} + +static ssize_t default_frame_cols_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", bus->prop.default_col); +} + +static ssize_t dynamic_frame_shape_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", bus->prop.dynamic_frame); +} + +static ssize_t command_error_threshold_show(struct sdw_bus *bus, + struct sdw_prop_attribute *attr, char *buf) +{ + return sprintf(buf, "0x%08x\n", bus->prop.err_threshold); +} + +static MASTER_ATTR(revision); +static MASTER_ATTR(count); +static MASTER_ATTR(clock_stop_modes); +static MASTER_ATTR(max_clock_frequency); +static MASTER_ATTR(clock_frequencies); +static MASTER_ATTR(clock_gears); +static MASTER_ATTR(default_frame_rows); +static MASTER_ATTR(default_frame_cols); +static MASTER_ATTR(dynamic_frame_shape); +static MASTER_ATTR(command_error_threshold); + +static struct attribute *master_node_attrs[] = { + &master_attr_revision.attr, + &master_attr_count.attr, + &master_attr_clock_stop_modes.attr, + &master_attr_max_clock_frequency.attr, + &master_attr_clock_frequencies.attr, + &master_attr_clock_gears.attr, + &master_attr_default_frame_rows.attr, + &master_attr_default_frame_cols.attr, + &master_attr_dynamic_frame_shape.attr, + &master_attr_command_error_threshold.attr, + NULL, +}; + +static const struct attribute_group sdw_master_node_group = { + .attrs = master_node_attrs, +}; + +int sdw_sysfs_bus_init(struct sdw_bus *bus) +{ + struct kset *sdw_bus_kset; + struct sdw_master_sysfs *master; + int err; + + if (bus->sysfs) { + dev_err(bus->dev, "SDW sysfs is already initialized\n"); + return -EIO; + } + + sdw_bus_kset = bus_get_kset(&sdw_bus_type); + + master = bus->sysfs = kzalloc(sizeof(*master), GFP_KERNEL); + if (!master) + return -ENOMEM; + + err = kobject_init_and_add(&master->kobj, + &sdw_master_prop_ktype, &sdw_bus_kset->kobj, + "mipi-properties-link%d", bus->link_id); + if (err < 0) + return err; + + master->bus = bus; + + err = sysfs_create_group(&master->kobj, &sdw_master_node_group); + if (err < 0) { + kobject_put(&master->kobj); + return err; + } + + kobject_uevent(&master->kobj, KOBJ_CHANGE); + return 0; +} + +void sdw_sysfs_bus_exit(struct sdw_bus *bus) +{ + struct sdw_master_sysfs *master = bus->sysfs; + + if (!master) + return; + + kobject_put(&master->kobj); + bus->sysfs = NULL; +} + +/* + * Slave sysfs + */ + +/* + * The sysfs for Slave reflects the MIPI description as given + * in the MIPI DisCo spec + * + * Base file is device + * |---- mipi_revision + * |---- wake_capable + * |---- test_mode_capable + * |---- simple_clk_stop_capable + * |---- clk_stop_timeout + * |---- ch_prep_timeout + * |---- reset_behave + * |---- high_PHY_capable + * |---- paging_support + * |---- bank_delay_support + * |---- p15_behave + * |---- master_count + * |---- source_ports + * |---- sink_ports + * |---- dp0 + * |---- max_word + * |---- min_word + * |---- words + * |---- flow_controlled + * |---- simple_ch_prep_sm + * |---- device_interrupts + * |---- dpN + * |---- max_word + * |---- min_word + * |---- words + * |---- type + * |---- max_grouping + * |---- simple_ch_prep_sm + * |---- ch_prep_timeout + * |---- device_interrupts + * |---- max_ch + * |---- min_ch + * |---- ch + * |---- ch_combinations + * |---- modes + * |---- max_async_buffer + * |---- block_pack_mode + * |---- port_encoding + * |---- bus_min_freq + * |---- bus_max_freq + * |---- bus_freq + * |---- max_freq + * |---- min_freq + * |---- freq + * |---- prep_ch_behave + * |---- glitchless + * + */ +struct sdw_slave_sysfs { + struct sdw_slave *slave; +}; + +#define SLAVE_ATTR(type) \ +static ssize_t type##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct sdw_slave *slave = dev_to_sdw_dev(dev); \ + return sprintf(buf, "0x%x\n", slave->prop.type); \ +} \ +static DEVICE_ATTR_RO(type) + +SLAVE_ATTR(mipi_revision); +SLAVE_ATTR(wake_capable); +SLAVE_ATTR(test_mode_capable); +SLAVE_ATTR(clk_stop_mode1); +SLAVE_ATTR(simple_clk_stop_capable); +SLAVE_ATTR(clk_stop_timeout); +SLAVE_ATTR(ch_prep_timeout); +SLAVE_ATTR(reset_behave); +SLAVE_ATTR(high_PHY_capable); +SLAVE_ATTR(paging_support); +SLAVE_ATTR(bank_delay_support); +SLAVE_ATTR(p15_behave); +SLAVE_ATTR(master_count); +SLAVE_ATTR(source_ports); + +static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + return sdw_slave_modalias(slave, buf, 256); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *slave_dev_attrs[] = { + &dev_attr_mipi_revision.attr, + &dev_attr_wake_capable.attr, + &dev_attr_test_mode_capable.attr, + &dev_attr_clk_stop_mode1.attr, + &dev_attr_simple_clk_stop_capable.attr, + &dev_attr_clk_stop_timeout.attr, + &dev_attr_ch_prep_timeout.attr, + &dev_attr_reset_behave.attr, + &dev_attr_high_PHY_capable.attr, + &dev_attr_paging_support.attr, + &dev_attr_bank_delay_support.attr, + &dev_attr_p15_behave.attr, + &dev_attr_master_count.attr, + &dev_attr_source_ports.attr, + &dev_attr_modalias.attr, + NULL, +}; + +static struct attribute_group sdw_slave_dev_attr_group = { + .attrs = slave_dev_attrs, +}; + +const struct attribute_group *sdw_slave_dev_attr_groups[] = { + &sdw_slave_dev_attr_group, + NULL +}; diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 861a910911b6..b04e7ed86162 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -300,6 +300,15 @@ int sdw_master_read_prop(struct sdw_bus *bus); int sdw_slave_read_prop(struct sdw_slave *slave);
/* + * SDW sysfs APIs + */ +struct sdw_slave_sysfs; +struct sdw_master_sysfs; + +int sdw_sysfs_bus_init(struct sdw_bus *bus); +void sdw_sysfs_bus_exit(struct sdw_bus *bus); + +/* * SDW Slave Structures and APIs */
@@ -355,6 +364,7 @@ struct sdw_slave_ops { * @bus: Bus handle * @ops: Slave callback ops * @prop: Slave properties + * @sysfs: Sysfs interface * @node: node for bus list * @port_ready: Port ready completion flag for each Slave port * @dev_num: Device Number assigned by Bus @@ -366,6 +376,7 @@ struct sdw_slave { struct sdw_bus *bus; const struct sdw_slave_ops *ops; struct sdw_slave_prop prop; + struct sdw_slave_sysfs *sysfs; struct list_head node; struct completion *port_ready; u16 dev_num; @@ -445,6 +456,7 @@ struct sdw_master_ops { * @msg_lock: message lock * @ops: Master callback ops * @prop: Master properties + * @sysfs: Bus sysfs * @defer_msg: Defer message * @clk_stop_timeout: Clock stop timeout computed */ @@ -457,6 +469,7 @@ struct sdw_bus { struct mutex msg_lock; const struct sdw_master_ops *ops; struct sdw_master_prop prop; + struct sdw_master_sysfs *sysfs; struct sdw_defer defer_msg; unsigned int clk_stop_timeout; };
Cadence IP implements SoundWire Master. Add base cadence library initialization and interrupt handling
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/Kconfig | 3 + drivers/soundwire/Makefile | 4 + drivers/soundwire/cadence_master.c | 429 +++++++++++++++++++++++++++++++++++++ drivers/soundwire/cadence_master.h | 34 +++ 4 files changed, 470 insertions(+) create mode 100644 drivers/soundwire/cadence_master.c create mode 100644 drivers/soundwire/cadence_master.h
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index 92a2c3bad87f..34361952310e 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -20,4 +20,7 @@ config SOUNDWIRE_BUS tristate select REGMAP_SOUNDWIRE
+config SOUNDWIRE_CADENCE + tristate + endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index 67dc7b546258..f9a1ce3a860e 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -5,3 +5,7 @@ #Bus Objs soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o sysfs.o obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o + +#Cadence Objs +soundwire-cadence-objs := cadence_master.o +obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c new file mode 100644 index 000000000000..ec7faf228812 --- /dev/null +++ b/drivers/soundwire/cadence_master.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * Cadence SoundWire Master module + * Used by Master driver + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw.h> +#include "bus.h" +#include "cadence_master.h" + +#define CDNS_MCP_CONFIG 0x0 + +#define CDNS_MCP_CONFIG_MCMD_RETRY GENMASK(27, 24) +#define CDNS_MCP_CONFIG_MPREQ_DELAY GENMASK(20, 16) +#define CDNS_MCP_CONFIG_MMASTER BIT(7) +#define CDNS_MCP_CONFIG_BUS_REL BIT(6) +#define CDNS_MCP_CONFIG_SNIFFER BIT(5) +#define CDNS_MCP_CONFIG_SSPMOD BIT(4) +#define CDNS_MCP_CONFIG_CMD BIT(3) +#define CDNS_MCP_CONFIG_OP GENMASK(2, 0) +#define CDNS_MCP_CONFIG_OP_NORMAL 0 + +#define CDNS_MCP_CONTROL 0x4 + +#define CDNS_MCP_CONTROL_RST_DELAY GENMASK(10, 8) +#define CDNS_MCP_CONTROL_CMD_RST BIT(7) +#define CDNS_MCP_CONTROL_SOFT_RST BIT(6) +#define CDNS_MCP_CONTROL_SW_RST BIT(5) +#define CDNS_MCP_CONTROL_HW_RST BIT(4) +#define CDNS_MCP_CONTROL_CLK_PAUSE BIT(3) +#define CDNS_MCP_CONTROL_CLK_STOP_CLR BIT(2) +#define CDNS_MCP_CONTROL_CMD_ACCEPT BIT(1) +#define CDNS_MCP_CONTROL_BLOCK_WAKEUP BIT(0) + + +#define CDNS_MCP_CMDCTRL 0x8 +#define CDNS_MCP_SSPSTAT 0xC +#define CDNS_MCP_FRAME_SHAPE 0x10 +#define CDNS_MCP_FRAME_SHAPE_INIT 0x14 + +#define CDNS_MCP_CONFIG_UPDATE 0x18 +#define CDNS_MCP_CONFIG_UPDATE_BIT BIT(0) + +#define CDNS_MCP_PHYCTRL 0x1C +#define CDNS_MCP_SSP_CTRL0 0x20 +#define CDNS_MCP_SSP_CTRL1 0x28 +#define CDNS_MCP_CLK_CTRL0 0x30 +#define CDNS_MCP_CLK_CTRL1 0x38 + +#define CDNS_MCP_STAT 0x40 + +#define CDNS_MCP_STAT_ACTIVE_BANK BIT(20) +#define CDNS_MCP_STAT_CLK_STOP BIT(16) + +#define CDNS_MCP_INTSTAT 0x44 +#define CDNS_MCP_INTMASK 0x48 + +#define CDNS_MCP_INT_IRQ BIT(31) +#define CDNS_MCP_INT_WAKEUP BIT(16) +#define CDNS_MCP_INT_SLAVE_RSVD BIT(15) +#define CDNS_MCP_INT_SLAVE_ALERT BIT(14) +#define CDNS_MCP_INT_SLAVE_ATTACH BIT(13) +#define CDNS_MCP_INT_SLAVE_NATTACH BIT(12) +#define CDNS_MCP_INT_SLAVE_MASK GENMASK(15, 12) +#define CDNS_MCP_INT_DPINT BIT(11) +#define CDNS_MCP_INT_CTRL_CLASH BIT(10) +#define CDNS_MCP_INT_DATA_CLASH BIT(9) +#define CDNS_MCP_INT_CMD_ERR BIT(7) +#define CDNS_MCP_INT_RX_WL BIT(2) +#define CDNS_MCP_INT_TXE BIT(1) + +#define CDNS_MCP_INTSET 0x4C + +#define CDNS_SDW_SLAVE_STAT 0x50 +#define CDNS_MCP_SLAVE_STAT_MASK BIT(1, 0) + +#define CDNS_MCP_SLAVE_INTSTAT0 0x54 +#define CDNS_MCP_SLAVE_INTSTAT1 0x58 +#define CDNS_MCP_SLAVE_INTSTAT_NPRESENT BIT(0) +#define CDNS_MCP_SLAVE_INTSTAT_ATTACHED BIT(1) +#define CDNS_MCP_SLAVE_INTSTAT_ALERT BIT(2) +#define CDNS_MCP_SLAVE_INTSTAT_RESERVED BIT(3) +#define CDNS_MCP_SLAVE_STATUS_BITS GENMASK(3, 0) +#define CDNS_MCP_SLAVE_STATUS_NUM 4 + +#define CDNS_MCP_SLAVE_INTMASK0 0x5C +#define CDNS_MCP_SLAVE_INTMASK1 0x60 + +#define CDNS_MCP_SLAVE_INTMASK0_MASK GENMASK(30, 0) +#define CDNS_MCP_SLAVE_INTMASK1_MASK GENMASK(16, 0) + +#define CDNS_MCP_PORT_INTSTAT 0x64 +#define CDNS_MCP_PDI_STAT 0x6C + +#define CDNS_MCP_FIFOLEVEL 0x78 +#define CDNS_MCP_FIFOSTAT 0x7C +#define CDNS_MCP_RX_FIFO_AVAIL GENMASK(5, 0) + +#define CDNS_MCP_CMD_BASE 0x80 +#define CDNS_MCP_RESP_BASE 0x80 +#define CDNS_MCP_CMD_LEN 0x20 +#define CDNS_MCP_CMD_WORD_LEN 0x4 + +#define CDNS_MCP_CMD_SSP_TAG BIT(31) +#define CDNS_MCP_CMD_COMMAND GENMASK(30, 28) +#define CDNS_MCP_CMD_DEV_ADDR GENMASK(27, 24) +#define CDNS_MCP_CMD_REG_ADDR_H GENMASK(23, 16) +#define CDNS_MCP_CMD_REG_ADDR_L GENMASK(15, 8) +#define CDNS_MCP_CMD_REG_DATA GENMASK(7, 0) + +#define CDNS_MCP_CMD_READ 2 +#define CDNS_MCP_CMD_WRITE 3 + +#define CDNS_MCP_RESP_RDATA GENMASK(15, 8) +#define CDNS_MCP_RESP_ACK BIT(0) +#define CDNS_MCP_RESP_NACK BIT(1) + +#define CDNS_DP_SIZE 128 + +#define CDNS_DPN_B0_CONFIG(n) (0x100 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_CH_EN(n) (0x104 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_SAMPLE_CTRL(n) (0x108 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_OFFSET_CTRL(n) (0x10C + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_HCTRL(n) (0x110 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_ASYNC_CTRL(n) (0x114 + CDNS_DP_SIZE * (n)) + +#define CDNS_DPN_B1_CONFIG(n) (0x118 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_CH_EN(n) (0x11C + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_SAMPLE_CTRL(n) (0x120 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_OFFSET_CTRL(n) (0x124 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_HCTRL(n) (0x128 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_ASYNC_CTRL(n) (0x12C + CDNS_DP_SIZE * (n)) + +#define CDNS_DPN_CONFIG_BPM BIT(18) +#define CDNS_DPN_CONFIG_BGC GENMASK(17, 16) +#define CDNS_DPN_CONFIG_WL GENMASK(12, 8) +#define CDNS_DPN_CONFIG_PORT_DAT GENMASK(3, 2) +#define CDNS_DPN_CONFIG_PORT_FLOW GENMASK(1, 0) + +#define CDNS_DPN_SAMPLE_CTRL_SI GENMASK(15, 0) + +#define CDNS_DPN_OFFSET_CTRL_1 GENMASK(7, 0) +#define CDNS_DPN_OFFSET_CTRL_2 GENMASK(15, 8) + +#define CDNS_DPN_HCTRL_HSTOP GENMASK(3, 0) +#define CDNS_DPN_HCTRL_HSTART GENMASK(7, 4) +#define CDNS_DPN_HCTRL_LCTRL GENMASK(10, 8) + +#define CDNS_PORTCTRL 0x130 +#define CDNS_PORTCTRL_DIRN BIT(7) +#define CDNS_PORTCTRL_BANK_INVERT BIT(8) + +#define CDNS_PORT_OFFSET 0x80 + +#define CDNS_PDI_CONFIG(n) (0x1100 + (n) * 16) + +#define CDNS_PDI_CONFIG_SOFT_RESET BIT(24) +#define CDNS_PDI_CONFIG_CHANNEL GENMASK(15, 8) +#define CDNS_PDI_CONFIG_PORT GENMASK(4, 0) + +/* Driver defaults */ + +#define CDNS_DEFAULT_CLK_DIVIDER 0 +#define CDNS_DEFAULT_FRAME_SHAPE 0x30 +#define CDNS_DEFAULT_SSP_INTERVAL 0x18 +#define CDNS_TX_TIMEOUT 2000 + +#define CDNS_PCM_PDI_OFFSET 0x2 +#define CDNS_PDM_PDI_OFFSET 0x6 + +#define CDNS_SCP_RX_FIFOLEVEL 0x2 + +/* + * register accessor helpers + */ +static inline u32 cdns_readl(struct sdw_cdns *cdns, int offset) +{ + return readl(cdns->registers + offset); +} + +static inline void cdns_writel(struct sdw_cdns *cdns, int offset, u32 value) +{ + writel(value, cdns->registers + offset); +} + +static inline void cdns_updatel(struct sdw_cdns *cdns, + int offset, u32 mask, u32 val) +{ + u32 tmp; + + tmp = cdns_readl(cdns, offset); + tmp = (tmp & ~mask) | val; + cdns_writel(cdns, offset, tmp); +} + +static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value) +{ + int timeout = 10; + u32 reg_read; + + writel(value, cdns->registers + offset); + + /* Wait for bit to be self cleared */ + do { + reg_read = readl(cdns->registers + offset); + if ((reg_read & value) == 0) + return 0; + + timeout--; + usleep_range(1, 50); + } while (timeout != 0); + + return -EIO; +} + +/* + * IRQ handling + */ + +static int cdns_update_slave_status(struct sdw_cdns *cdns, + u32 slave0, u32 slave1) +{ + enum sdw_slave_status status[SDW_MAX_DEVICES + 1]; + bool is_slave = false; + u64 slave, mask; + int i, set_status; + + /* combine the two status */ + slave = ((u64)slave1 << 32) | slave0; + memset(status, 0, sizeof(status)); + + for (i = 0; i <= SDW_MAX_DEVICES; i++) { + mask = (slave >> (i * CDNS_MCP_SLAVE_STATUS_NUM)) & + CDNS_MCP_SLAVE_STATUS_BITS; + if (!mask) + continue; + + is_slave = true; + set_status = 0; + + if (mask & CDNS_MCP_SLAVE_INTSTAT_RESERVED) { + status[i] = SDW_SLAVE_RESERVED; + set_status++; + } + + if (mask & CDNS_MCP_SLAVE_INTSTAT_ATTACHED) { + status[i] = SDW_SLAVE_ATTACHED; + set_status++; + } + + if (mask & CDNS_MCP_SLAVE_INTSTAT_ALERT) { + status[i] = SDW_SLAVE_ALERT; + set_status++; + } + + if (mask & CDNS_MCP_SLAVE_INTSTAT_NPRESENT) { + status[i] = SDW_SLAVE_UNATTACHED; + set_status++; + } + + /* first check if Slave reported multiple status */ + if (set_status > 1) { + dev_warn(cdns->dev, + "Slave reported multiple Status: %d\n", + status[i]); + /* + * TODO: we need to reread the status here by + * issuing a PING cmd + */ + } + } + + if (is_slave) + return sdw_handle_slave_status(&cdns->bus, status); + + return 0; +} + +/** + * sdw_cdns_irq() - Cadence interrupt handler + * @irq: irq number + * @dev_id: irq context + */ +irqreturn_t sdw_cdns_irq(int irq, void *dev_id) +{ + struct sdw_cdns *cdns = dev_id; + u32 int_status; + int ret = IRQ_HANDLED; + + /* Check if the link is up */ + if (!cdns->link_up) + return IRQ_NONE; + + int_status = cdns_readl(cdns, CDNS_MCP_INTSTAT); + + if (!(int_status & CDNS_MCP_INT_IRQ)) + return IRQ_NONE; + + if (int_status & CDNS_MCP_INT_CTRL_CLASH) { + + /* Slave is driving bit slot during control word */ + dev_err_ratelimited(cdns->dev, "Bus clash for control word\n"); + } + + if (int_status & CDNS_MCP_INT_DATA_CLASH) { + /* + * Multiple slaves trying to drive bit slot, or issue with + * ownership of data bits or Slave gone bonkers + */ + dev_err_ratelimited(cdns->dev, "Bus clash for data word\n"); + } + + if (int_status & CDNS_MCP_INT_SLAVE_MASK) { + /* Mask the Slave interrupt and wake thread */ + cdns_updatel(cdns, CDNS_MCP_INTMASK, + CDNS_MCP_INT_SLAVE_MASK, 0); + + int_status &= ~CDNS_MCP_INT_SLAVE_MASK; + ret = IRQ_WAKE_THREAD; + } + + cdns_writel(cdns, CDNS_MCP_INTSTAT, int_status); + return ret; +} +EXPORT_SYMBOL(sdw_cdns_irq); + +/** + * sdw_cdns_thread() - Cadence irq thread handler + * @irq: irq number + * @dev_id: irq context + */ +irqreturn_t sdw_cdns_thread(int irq, void *dev_id) +{ + struct sdw_cdns *cdns = dev_id; + u32 slave0, slave1; + + dev_dbg(cdns->dev, "Slave status change\n"); + + slave0 = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT0); + slave1 = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT1); + + cdns_update_slave_status(cdns, slave0, slave1); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT0, slave0); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT1, slave1); + + /* clear and unmask Slave interrupt now */ + cdns_writel(cdns, CDNS_MCP_INTSTAT, CDNS_MCP_INT_SLAVE_MASK); + cdns_updatel(cdns, CDNS_MCP_INTMASK, + CDNS_MCP_INT_SLAVE_MASK, CDNS_MCP_INT_SLAVE_MASK); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL(sdw_cdns_thread); + +/* + * init routines + */ + +/** + * sdw_cdns_init() - Cadence initialization + * @cdns: Cadence instance + */ +int sdw_cdns_init(struct sdw_cdns *cdns) +{ + u32 val; + int ret; + + /* Exit clock stop */ + ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CLK_STOP_CLR); + if (ret < 0) { + dev_err(cdns->dev, "Couldn't exit from clock stop\n"); + return ret; + } + + /* Set clock divider */ + val = cdns_readl(cdns, CDNS_MCP_CLK_CTRL0); + val |= CDNS_DEFAULT_CLK_DIVIDER; + cdns_writel(cdns, CDNS_MCP_CLK_CTRL0, val); + + /* Set the default frame shape */ + cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, CDNS_DEFAULT_FRAME_SHAPE); + + /* Set SSP interval to default value */ + cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, CDNS_DEFAULT_SSP_INTERVAL); + cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, CDNS_DEFAULT_SSP_INTERVAL); + + /* Set cmd accept mode */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT, + CDNS_MCP_CONTROL_CMD_ACCEPT); + + /* Configure mcp config */ + val = cdns_readl(cdns, CDNS_MCP_CONFIG); + + /* Set Max cmd retry to 15 */ + val |= CDNS_MCP_CONFIG_MCMD_RETRY; + + /* Set frame delay between PREQ and ping frame to 15 frames */ + val |= 0xF << SDW_REG_SHIFT(CDNS_MCP_CONFIG_MPREQ_DELAY); + + /* Disable auto bus release */ + val &= ~CDNS_MCP_CONFIG_BUS_REL; + + /* Disable sniffer mode */ + val &= ~CDNS_MCP_CONFIG_SNIFFER; + + /* Set cmd mode for Tx and Rx cmds */ + val &= ~CDNS_MCP_CONFIG_CMD; + + /* Set operation to normal */ + val &= ~CDNS_MCP_CONFIG_OP; + val |= CDNS_MCP_CONFIG_OP_NORMAL; + + cdns_writel(cdns, CDNS_MCP_CONFIG, val); + + return 0; +} +EXPORT_SYMBOL(sdw_cdns_init); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Cadence Soundwire Library"); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h new file mode 100644 index 000000000000..f723c3395c0f --- /dev/null +++ b/drivers/soundwire/cadence_master.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_CADENCE_H +#define __SDW_CADENCE_H + +/** + * struct sdw_cdns - Cadence driver context + * @dev: Linux device + * @bus: Bus handle + * @instance: instance number + * @registers: Cadence registers + * @link_up: Link status + */ +struct sdw_cdns { + struct device *dev; + struct sdw_bus bus; + unsigned int instance; + + void __iomem *registers; + + bool link_up; +}; + +#define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus) + +/* Exported symbols */ + +irqreturn_t sdw_cdns_irq(int irq, void *dev_id); +irqreturn_t sdw_cdns_thread(int irq, void *dev_id); + +int sdw_cdns_init(struct sdw_cdns *cdns); + +#endif /* __SDW_CADENCE_H */
From: Sanyog Kale sanyog.r.kale@intel.com
Implement sdw_master_ops with support for xfer_msg, xfer_msg_defer and reset_page_addr. Since Cadence module doesn't know the systems it will be used, set the read_prop to the bus helper.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/cadence_master.c | 320 +++++++++++++++++++++++++++++++++++++ drivers/soundwire/cadence_master.h | 14 ++ 2 files changed, 334 insertions(+)
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index ec7faf228812..4790e82562fa 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -222,9 +222,261 @@ static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value) }
/* + * IO Calls + */ +static enum sdw_command_response cdns_fill_msg_resp( + struct sdw_cdns *cdns, + struct sdw_msg *msg, int count, int offset) +{ + int nack = 0, no_ack = 0; + int i; + + /* check message response */ + for (i = 0; i < count; i++) { + if (!(cdns->response_buf[i] & CDNS_MCP_RESP_ACK)) { + no_ack = 1; + dev_dbg(cdns->dev, "Msg Ack not received\n"); + if (cdns->response_buf[i] & CDNS_MCP_RESP_NACK) { + nack = 1; + dev_err(cdns->dev, "Msg NACK received\n"); + } + } + } + + if (nack) { + dev_err(cdns->dev, "Msg NACKed for Slave %d\n", msg->dev_num); + return SDW_CMD_FAIL; + } else if (no_ack) { + dev_dbg(cdns->dev, "Msg ignored for Slave %d\n", msg->dev_num); + return SDW_CMD_IGNORED; + } + + /* fill response */ + for (i = 0; i < count; i++) + msg->buf[i + offset] = cdns->response_buf[i] >> + SDW_REG_SHIFT(CDNS_MCP_RESP_RDATA); + + return SDW_CMD_OK; +} + +static enum sdw_command_response +_cdns_xfer_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int cmd, + int offset, int count, bool defer) +{ + unsigned long time; + u32 base, i, data; + u16 addr; + + /* Program the watermark level for RX FIFO */ + if (cdns->msg_count != count) { + cdns_writel(cdns, CDNS_MCP_FIFOLEVEL, count); + cdns->msg_count = count; + } + + base = CDNS_MCP_CMD_BASE; + addr = msg->addr; + + for (i = 0; i < count; i++) { + data = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR); + data |= cmd << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND); + data |= addr++ << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L); + + if (msg->flags == SDW_MSG_FLAG_WRITE) + data |= msg->buf[i + offset]; + + data |= msg->ssp_sync << SDW_REG_SHIFT(CDNS_MCP_CMD_SSP_TAG); + cdns_writel(cdns, base, data); + base += CDNS_MCP_CMD_WORD_LEN; + } + + if (defer) + return SDW_CMD_OK; + + /* wait for timeout or response */ + time = wait_for_completion_timeout(&cdns->tx_complete, + msecs_to_jiffies(CDNS_TX_TIMEOUT)); + if (!time) { + dev_err(cdns->dev, "IO transfer timed out\n"); + msg->len = 0; + return SDW_CMD_TIMEOUT; + } + + return cdns_fill_msg_resp(cdns, msg, count, offset); +} + +static enum sdw_command_response cdns_program_scp_addr( + struct sdw_cdns *cdns, struct sdw_msg *msg) +{ + int nack = 0, no_ack = 0; + unsigned long time; + u32 data[2], base; + int i; + + /* Program the watermark level for RX FIFO */ + if (cdns->msg_count != CDNS_SCP_RX_FIFOLEVEL) { + cdns_writel(cdns, CDNS_MCP_FIFOLEVEL, CDNS_SCP_RX_FIFOLEVEL); + cdns->msg_count = CDNS_SCP_RX_FIFOLEVEL; + } + + data[0] = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR); + data[0] |= 0x3 << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND); + data[1] = data[0]; + + data[0] |= SDW_SCP_ADDRPAGE1 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L); + data[1] |= SDW_SCP_ADDRPAGE2 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L); + + data[0] |= msg->addr_page1; + data[1] |= msg->addr_page2; + + base = CDNS_MCP_CMD_BASE; + cdns_writel(cdns, base, data[0]); + base += CDNS_MCP_CMD_WORD_LEN; + cdns_writel(cdns, base, data[1]); + + time = wait_for_completion_timeout(&cdns->tx_complete, + msecs_to_jiffies(CDNS_TX_TIMEOUT)); + if (!time) { + dev_err(cdns->dev, "SCP Msg trf timed out\n"); + msg->len = 0; + return SDW_CMD_TIMEOUT; + } + + /* check response the writes */ + for (i = 0; i < 2; i++) { + if (!(cdns->response_buf[i] & CDNS_MCP_RESP_ACK)) { + no_ack = 1; + dev_err(cdns->dev, "Program SCP Ack not received"); + if (cdns->response_buf[i] & CDNS_MCP_RESP_NACK) { + nack = 1; + dev_err(cdns->dev, "Program SCP NACK received"); + } + } + } + + /* For NACK, NO ack, don't return err if we are in Broadcast mode */ + if (nack) { + dev_err(cdns->dev, + "SCP_addrpage NACKed for Slave %d", msg->dev_num); + return SDW_CMD_FAIL; + } else if (no_ack) { + dev_dbg(cdns->dev, + "SCP_addrpage ignored for Slave %d", msg->dev_num); + return SDW_CMD_IGNORED; + } + + return SDW_CMD_OK; +} + +static int cdns_prep_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int *cmd) +{ + int ret; + + if (msg->page) { + ret = cdns_program_scp_addr(cdns, msg); + if (ret) { + msg->len = 0; + return ret; + } + } + + switch (msg->flags) { + case SDW_MSG_FLAG_READ: + *cmd = CDNS_MCP_CMD_READ; + break; + + case SDW_MSG_FLAG_WRITE: + *cmd = CDNS_MCP_CMD_WRITE; + break; + + default: + dev_err(cdns->dev, "Invalid msg cmd: %d\n", msg->flags); + return -EINVAL; + } + + return 0; +} + +static enum sdw_command_response +cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + int cmd = 0, ret, i; + + ret = cdns_prep_msg(cdns, msg, &cmd); + if (ret) + return SDW_CMD_FAIL_OTHER; + + for (i = 0; i < msg->len / CDNS_MCP_CMD_LEN; i++) { + ret = _cdns_xfer_msg(cdns, msg, cmd, i * CDNS_MCP_CMD_LEN, + CDNS_MCP_CMD_LEN, false); + if (ret < 0) + goto exit; + } + + if (!(msg->len % CDNS_MCP_CMD_LEN)) + goto exit; + + ret = _cdns_xfer_msg(cdns, msg, cmd, i * CDNS_MCP_CMD_LEN, + msg->len % CDNS_MCP_CMD_LEN, false); + +exit: + return ret; +} + +static enum sdw_command_response +cdns_xfer_msg_defer(struct sdw_bus *bus, + struct sdw_msg *msg, struct sdw_defer *defer) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + int cmd = 0, ret; + + /* for defer only 1 message is supported */ + if (msg->len > 1) + return -ENOTSUPP; + + ret = cdns_prep_msg(cdns, msg, &cmd); + if (ret) + return SDW_CMD_FAIL_OTHER; + + cdns->defer = defer; + cdns->defer->length = msg->len; + + return _cdns_xfer_msg(cdns, msg, cmd, 0, msg->len, true); +} + +static enum sdw_command_response +cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + struct sdw_msg msg; + + /* Create dummy message with valid device number */ + memset(&msg, 0, sizeof(msg)); + msg.dev_num = dev_num; + + return cdns_program_scp_addr(cdns, &msg); +} + +/* * IRQ handling */
+static void cdns_read_response(struct sdw_cdns *cdns) +{ + u32 num_resp, cmd_base; + int i; + + num_resp = cdns_readl(cdns, CDNS_MCP_FIFOSTAT); + num_resp &= CDNS_MCP_RX_FIFO_AVAIL; + + cmd_base = CDNS_MCP_CMD_BASE; + + for (i = 0; i < num_resp; i++) { + cdns->response_buf[i] = cdns_readl(cdns, cmd_base); + cmd_base += CDNS_MCP_CMD_WORD_LEN; + } +} + static int cdns_update_slave_status(struct sdw_cdns *cdns, u32 slave0, u32 slave1) { @@ -304,6 +556,18 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) if (!(int_status & CDNS_MCP_INT_IRQ)) return IRQ_NONE;
+ if (int_status & CDNS_MCP_INT_RX_WL) { + cdns_read_response(cdns); + + if (cdns->defer) { + cdns_fill_msg_resp(cdns, cdns->defer->msg, + cdns->defer->length, 0); + complete(&cdns->defer->complete); + cdns->defer = NULL; + } else + complete(&cdns->tx_complete); + } + if (int_status & CDNS_MCP_INT_CTRL_CLASH) {
/* Slave is driving bit slot during control word */ @@ -363,6 +627,42 @@ EXPORT_SYMBOL(sdw_cdns_thread); /* * init routines */ +static int _cdns_enable_interrupt(struct sdw_cdns *cdns) +{ + u32 mask; + + cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK0, + CDNS_MCP_SLAVE_INTMASK0_MASK); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK1, + CDNS_MCP_SLAVE_INTMASK1_MASK); + + mask = CDNS_MCP_INT_SLAVE_RSVD | CDNS_MCP_INT_SLAVE_ALERT | + CDNS_MCP_INT_SLAVE_ATTACH | CDNS_MCP_INT_SLAVE_NATTACH | + CDNS_MCP_INT_CTRL_CLASH | CDNS_MCP_INT_DATA_CLASH | + CDNS_MCP_INT_RX_WL | CDNS_MCP_INT_IRQ | CDNS_MCP_INT_DPINT; + + cdns_writel(cdns, CDNS_MCP_INTMASK, mask); + + return 0; +} + +/** + * sdw_cdns_enable_interrupt() - Enable SDW interrupts and update config + * @cdns: Cadence instance + */ +int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns) +{ + int ret; + + _cdns_enable_interrupt(cdns); + ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE, + CDNS_MCP_CONFIG_UPDATE_BIT); + if (ret < 0) + dev_err(cdns->dev, "Config update timedout"); + + return ret; +} +EXPORT_SYMBOL(sdw_cdns_enable_interrupt);
/** * sdw_cdns_init() - Cadence initialization @@ -425,5 +725,25 @@ int sdw_cdns_init(struct sdw_cdns *cdns) } EXPORT_SYMBOL(sdw_cdns_init);
+struct sdw_master_ops sdw_cdns_master_ops = { + .read_prop = sdw_master_read_prop, + .xfer_msg = cdns_xfer_msg, + .xfer_msg_defer = cdns_xfer_msg_defer, + .reset_page_addr = cdns_reset_page_addr, +}; +EXPORT_SYMBOL(sdw_cdns_master_ops); + +/** + * sdw_cdns_probe() - Cadence probe routine + * @cdns: Cadence instance + */ +int sdw_cdns_probe(struct sdw_cdns *cdns) +{ + init_completion(&cdns->tx_complete); + + return 0; +} +EXPORT_SYMBOL(sdw_cdns_probe); + MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("Cadence Soundwire Library"); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index f723c3395c0f..beaf6c9804eb 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -9,26 +9,40 @@ * @dev: Linux device * @bus: Bus handle * @instance: instance number + * @response_buf: SoundWire response buffer + * @tx_complete: Tx completion + * @defer: Defer pointer * @registers: Cadence registers * @link_up: Link status + * @msg_count: Messages sent on bus */ struct sdw_cdns { struct device *dev; struct sdw_bus bus; unsigned int instance;
+ u32 response_buf[0x80]; + struct completion tx_complete; + struct sdw_defer *defer; + void __iomem *registers;
bool link_up; + unsigned int msg_count; };
#define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus)
/* Exported symbols */
+int sdw_cdns_probe(struct sdw_cdns *cdns); +extern struct sdw_master_ops sdw_cdns_master_ops; + irqreturn_t sdw_cdns_irq(int irq, void *dev_id); irqreturn_t sdw_cdns_thread(int irq, void *dev_id);
int sdw_cdns_init(struct sdw_cdns *cdns); +int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns); +
#endif /* __SDW_CADENCE_H */
struct sdw_cdns { struct device *dev; struct sdw_bus bus; unsigned int instance;
- u32 response_buf[0x80];
last nit-pick: use a #define for the size. Also are you sure about the value? I vaguely recall that the FIFO was 32-deep, you are confusing bytes and words here?
struct completion tx_complete;
struct sdw_defer *defer;
void __iomem *registers;
bool link_up;
unsigned int msg_count; };
On Fri, Dec 01, 2017 at 06:02:24PM -0600, Pierre-Louis Bossart wrote:
struct sdw_cdns { struct device *dev; struct sdw_bus bus; unsigned int instance;
- u32 response_buf[0x80];
last nit-pick: use a #define for the size.
okay last one huh :) yeah will add.
Also are you sure about the value? I vaguely recall that the FIFO was 32-deep, you are confusing bytes and words here?
I dont think this is related to FIFO depth, this is buffer to hold values. Will check though..
Some Intel platforms have SoundWire Master, so add Intel SoundWire Master driver which uses Cadence module. This patch adds probe and initialization routines for Intel Master driver.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/Kconfig | 11 ++ drivers/soundwire/Makefile | 4 + drivers/soundwire/intel.c | 347 ++++++++++++++++++++++++++++++++++++ drivers/soundwire/intel.h | 23 +++ include/linux/soundwire/sdw_intel.h | 21 +++ 5 files changed, 406 insertions(+) create mode 100644 drivers/soundwire/intel.c create mode 100644 drivers/soundwire/intel.h create mode 100644 include/linux/soundwire/sdw_intel.h
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index 34361952310e..b46084b4b1f8 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -23,4 +23,15 @@ config SOUNDWIRE_BUS config SOUNDWIRE_CADENCE tristate
+config SOUNDWIRE_INTEL + tristate "Intel SoundWire Master driver" + select SOUNDWIRE_CADENCE + select SOUNDWIRE_BUS + depends on X86 && ACPI + ---help--- + SoundWire Intel Master driver. + If you have an Intel platform which has a SoundWire Master then + enable this config option to get the SoundWire support for that + device. + endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index f9a1ce3a860e..0813b2c74426 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -9,3 +9,7 @@ obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o #Cadence Objs soundwire-cadence-objs := cadence_master.o obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o + +#Intel driver +soundwire-intel-objs := intel.o +obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c new file mode 100644 index 000000000000..1d37be3537c7 --- /dev/null +++ b/drivers/soundwire/intel.c @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * Soundwire Intel Master Driver + */ + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_intel.h> +#include "cadence_master.h" +#include "intel.h" + +/* Intel SHIM Registers Definition */ +#define SDW_SHIM_LCAP 0x0 +#define SDW_SHIM_LCTL 0x4 +#define SDW_SHIM_IPPTR 0x8 +#define SDW_SHIM_SYNC 0xC + +#define SDW_SHIM_CTLSCAP(x) (0x010 + 0x60 * x) +#define SDW_SHIM_CTLS0CM(x) (0x012 + 0x60 * x) +#define SDW_SHIM_CTLS1CM(x) (0x014 + 0x60 * x) +#define SDW_SHIM_CTLS2CM(x) (0x016 + 0x60 * x) +#define SDW_SHIM_CTLS3CM(x) (0x018 + 0x60 * x) +#define SDW_SHIM_PCMSCAP(x) (0x020 + 0x60 * x) + +#define SDW_SHIM_PCMSYCHM(x, y) (0x022 + (0x60 * x) + (0x2 * y)) +#define SDW_SHIM_PCMSYCHC(x, y) (0x042 + (0x60 * x) + (0x2 * y)) +#define SDW_SHIM_PDMSCAP(x) (0x062 + 0x60 * x) +#define SDW_SHIM_IOCTL(x) (0x06C + 0x60 * x) +#define SDW_SHIM_CTMCTL(x) (0x06E + 0x60 * x) + +#define SDW_SHIM_WAKEEN 0x190 +#define SDW_SHIM_WAKESTS 0x192 + +#define SDW_SHIM_LCTL_SPA BIT(0) +#define SDW_SHIM_LCTL_CPA BIT(8) + +#define SDW_SHIM_SYNC_SYNCPRD_VAL 0x176F +#define SDW_SHIM_SYNC_SYNCPRD GENMASK(14, 0) +#define SDW_SHIM_SYNC_SYNCCPU BIT(15) +#define SDW_SHIM_SYNC_CMDSYNC_MASK GENMASK(19, 16) +#define SDW_SHIM_SYNC_CMDSYNC BIT(16) +#define SDW_SHIM_SYNC_SYNCGO BIT(24) + +#define SDW_SHIM_PCMSCAP_ISS GENMASK(3, 0) +#define SDW_SHIM_PCMSCAP_OSS GENMASK(7, 4) +#define SDW_SHIM_PCMSCAP_BSS GENMASK(12, 8) + +#define SDW_SHIM_PCMSYCM_LCHN GENMASK(3, 0) +#define SDW_SHIM_PCMSYCM_HCHN GENMASK(7, 4) +#define SDW_SHIM_PCMSYCM_STREAM GENMASK(13, 8) +#define SDW_SHIM_PCMSYCM_DIR BIT(15) + +#define SDW_SHIM_PDMSCAP_ISS GENMASK(3, 0) +#define SDW_SHIM_PDMSCAP_OSS GENMASK(7, 4) +#define SDW_SHIM_PDMSCAP_BSS GENMASK(12, 8) +#define SDW_SHIM_PDMSCAP_CPSS GENMASK(15, 13) + +#define SDW_SHIM_IOCTL_MIF BIT(0) +#define SDW_SHIM_IOCTL_CO BIT(1) +#define SDW_SHIM_IOCTL_COE BIT(2) +#define SDW_SHIM_IOCTL_DO BIT(3) +#define SDW_SHIM_IOCTL_DOE BIT(4) +#define SDW_SHIM_IOCTL_BKE BIT(5) +#define SDW_SHIM_IOCTL_WPDD BIT(6) +#define SDW_SHIM_IOCTL_CIBD BIT(8) +#define SDW_SHIM_IOCTL_DIBD BIT(9) + +#define SDW_SHIM_CTMCTL_DACTQE BIT(0) +#define SDW_SHIM_CTMCTL_DODS BIT(1) +#define SDW_SHIM_CTMCTL_DOAIS GENMASK(4, 3) + +#define SDW_SHIM_WAKEEN_ENABLE BIT(0) +#define SDW_SHIM_WAKESTS_STATUS BIT(0) + +/* Intel ALH Register definitions */ +#define SDW_ALH_STRMZCFG(x) (0x000 + (0x4 * x)) + +#define SDW_ALH_STRMZCFG_DMAT_VAL 0x3 +#define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0) +#define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16) + +struct sdw_intel { + struct sdw_cdns cdns; + int instance; + struct sdw_intel_link_res *res; +}; + +#define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns) + +/* + * Read, write helpers for HW registers + */ +static inline int intel_readl(void __iomem *base, int offset) +{ + return readl(base + offset); +} + +static inline void intel_writel(void __iomem *base, int offset, int value) +{ + writel(value, base + offset); +} + +static inline u16 intel_readw(void __iomem *base, int offset) +{ + return readw(base + offset); +} + +static inline void intel_writew(void __iomem *base, int offset, u16 value) +{ + writew(value, base + offset); +} + +static int intel_clear_bit(void __iomem *base, int offset, u32 value, u32 mask) +{ + int timeout = 10; + u32 reg_read; + + writel(value, base + offset); + do { + reg_read = readl(base + offset); + if (!(reg_read & mask)) + return 0; + + timeout--; + usleep_range(1, 50); + + } while (timeout != 0); + + return -EAGAIN; +} + +static int intel_set_bit(void __iomem *base, int offset, u32 value, u32 mask) +{ + int timeout = 10; + u32 reg_read; + + writel(value, base + offset); + do { + reg_read = readl(base + offset); + if (reg_read & mask) + return 0; + + timeout--; + usleep_range(1, 50); + + } while (timeout != 0); + + return -EAGAIN; +} + +/* + * shim ops + */ + +static int intel_link_power_up(struct sdw_intel *sdw) +{ + unsigned int link_id = sdw->instance; + void __iomem *shim = sdw->res->shim; + int spa_mask, cpa_mask; + int link_control, ret; + + /* Link power up sequence */ + link_control = intel_readl(shim, SDW_SHIM_LCTL); + spa_mask = (SDW_SHIM_LCTL_SPA << link_id); + cpa_mask = (SDW_SHIM_LCTL_SPA << link_id); + link_control |= spa_mask; + + ret = intel_set_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); + if (ret < 0) + return ret; + + sdw->cdns.link_up = true; + return 0; +} + +static int intel_shim_init(struct sdw_intel *sdw) +{ + void __iomem *shim = sdw->res->shim; + unsigned int link_id = sdw->instance; + int sync_reg, ret; + u16 ioctl = 0, act = 0; + + /* Initialize Shim */ + ioctl |= SDW_SHIM_IOCTL_BKE; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl |= SDW_SHIM_IOCTL_WPDD; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl |= SDW_SHIM_IOCTL_DO; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl |= SDW_SHIM_IOCTL_DOE; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + /* Switch to MIP from Glue logic */ + ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); + + ioctl &= ~(SDW_SHIM_IOCTL_DOE); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl &= ~(SDW_SHIM_IOCTL_DO); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl |= (SDW_SHIM_IOCTL_MIF); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + ioctl &= ~(SDW_SHIM_IOCTL_BKE); + ioctl &= ~(SDW_SHIM_IOCTL_COE); + + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + + act |= 0x1 << SDW_REG_SHIFT(SDW_SHIM_CTMCTL_DOAIS); + act |= SDW_SHIM_CTMCTL_DACTQE; + act |= SDW_SHIM_CTMCTL_DODS; + intel_writew(shim, SDW_SHIM_CTMCTL(link_id), act); + + /* Now set SyncPRD period */ + sync_reg = intel_readl(shim, SDW_SHIM_SYNC); + sync_reg |= (SDW_SHIM_SYNC_SYNCPRD_VAL << + SDW_REG_SHIFT(SDW_SHIM_SYNC_SYNCPRD)); + + /* Set SyncCPU bit */ + sync_reg |= SDW_SHIM_SYNC_SYNCCPU; + ret = intel_clear_bit(shim, SDW_SHIM_SYNC, sync_reg, + SDW_SHIM_SYNC_SYNCCPU); + if (ret < 0) + dev_err(sdw->cdns.dev, "Failed to set sync period: %d", ret); + + return ret; +} + +static int intel_prop_read(struct sdw_bus *bus) +{ + /* Initialize with default handler to read all DisCo properties */ + sdw_master_read_prop(bus); + + /* BIOS is not giving some values correctly. So, lets override them */ + bus->prop.num_freq = 1; + bus->prop.freq = devm_kcalloc(bus->dev, sizeof(*bus->prop.freq), + bus->prop.num_freq, GFP_KERNEL); + if (!bus->prop.freq) + return -ENOMEM; + + bus->prop.freq[0] = bus->prop.max_freq; + bus->prop.err_threshold = 5; + + return 0; +} + +/* + * probe and init + */ +static int intel_probe(struct platform_device *pdev) +{ + struct sdw_intel *sdw; + int ret; + + sdw = devm_kzalloc(&pdev->dev, sizeof(*sdw), GFP_KERNEL); + if (!sdw) + return -ENOMEM; + + sdw->instance = pdev->id; + sdw->res = dev_get_platdata(&pdev->dev); + sdw->cdns.dev = &pdev->dev; + sdw->cdns.registers = sdw->res->registers; + sdw->cdns.instance = sdw->instance; + sdw->cdns.msg_count = 0; + sdw->cdns.bus.dev = &pdev->dev; + sdw->cdns.bus.link_id = pdev->id; + + sdw_cdns_probe(&sdw->cdns); + + /* Set property read ops */ + sdw_cdns_master_ops.read_prop = intel_prop_read; + sdw->cdns.bus.ops = &sdw_cdns_master_ops; + + platform_set_drvdata(pdev, sdw); + + ret = sdw_add_bus_master(&sdw->cdns.bus); + if (ret) { + dev_err(&pdev->dev, "sdw_add_bus_master fail: %d\n", ret); + goto err_master_reg; + } + + /* Initialize shim and controller */ + intel_link_power_up(sdw); + intel_shim_init(sdw); + + ret = sdw_cdns_init(&sdw->cdns); + if (ret) + goto err_init; + + sdw_cdns_enable_interrupt(&sdw->cdns); + if (ret) + goto err_init; + + /* Acquire IRQ */ + ret = request_threaded_irq(sdw->res->irq, sdw_cdns_irq, + sdw_cdns_thread, IRQF_SHARED, KBUILD_MODNAME, + &sdw->cdns); + if (ret < 0) { + dev_err(sdw->cdns.dev, "unable to grab IRQ %d, disabling device\n", + sdw->res->irq); + goto err_init; + } + + return 0; + +err_init: + sdw_delete_bus_master(&sdw->cdns.bus); +err_master_reg: + return ret; +} + +static int intel_remove(struct platform_device *pdev) +{ + struct sdw_intel *sdw; + + sdw = platform_get_drvdata(pdev); + + free_irq(sdw->res->irq, sdw); + sdw_delete_bus_master(&sdw->cdns.bus); + + return 0; +} + +static struct platform_driver sdw_intel_drv = { + .probe = intel_probe, + .remove = intel_remove, + .driver = { + .name = "int-sdw", + + }, +}; + +module_platform_driver(sdw_intel_drv); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:int-sdw"); +MODULE_DESCRIPTION("Intel Soundwire Master Driver"); diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h new file mode 100644 index 000000000000..ffa30d9535a2 --- /dev/null +++ b/drivers/soundwire/intel.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_INTEL_LOCAL_H +#define __SDW_INTEL_LOCAL_H + +/** + * struct sdw_intel_res - Soundwire link resources + * @registers: Link IO registers base + * @shim: Audio shim pointer + * @alh: ALH (Audio Link Hub) pointer + * @irq: Interrupt line + * + * This is set as pdata for each link instance. + */ +struct sdw_intel_link_res { + void __iomem *registers; + void __iomem *shim; + void __iomem *alh; + int irq; +}; + +#endif /* __SDW_INTEL_LOCAL_H */ diff --git a/include/linux/soundwire/sdw_intel.h b/include/linux/soundwire/sdw_intel.h new file mode 100644 index 000000000000..370cc52d8d1e --- /dev/null +++ b/include/linux/soundwire/sdw_intel.h @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#ifndef __SDW_INTEL_H +#define __SDW_INTEL_H + +/** + * struct sdw_intel_res - Soundwire Intel resource structure + * @mmio_base: mmio base of SoundWire registers + * @irq: interrupt number + * @handle: ACPI parent handle + * @parent: parent device + */ +struct sdw_intel_res { + void __iomem *mmio_base; + int irq; + acpi_handle handle; + struct device *parent; +}; + +#endif
The SoundWire Master is implemented as part of Audio controller in Intel platforms. Add a init module which creates SoundWire Master platform devices based on the links supported in the hardware.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/Makefile | 3 + drivers/soundwire/intel_init.c | 198 ++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw_intel.h | 3 + 3 files changed, 204 insertions(+) create mode 100644 drivers/soundwire/intel_init.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index 0813b2c74426..94272c965d6c 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -13,3 +13,6 @@ obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o #Intel driver soundwire-intel-objs := intel.o obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o + +soundwire-intel-init-objs := intel_init.o +obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel-init.o diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c new file mode 100644 index 000000000000..6f2bb99526f2 --- /dev/null +++ b/drivers/soundwire/intel_init.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * SDW Intel Init Routines + * + * Initializes and creates SDW devices based on ACPI and Hardware values + */ + +#include <linux/acpi.h> +#include <linux/platform_device.h> +#include <linux/soundwire/sdw_intel.h> +#include "intel.h" + +#define SDW_MAX_LINKS 4 +#define SDW_SHIM_LCAP 0x0 +#define SDW_SHIM_BASE 0x2C000 +#define SDW_ALH_BASE 0x2C800 +#define SDW_LINK_BASE 0x30000 +#define SDW_LINK_SIZE 0x10000 + +struct sdw_link_data { + struct sdw_intel_link_res res; + struct platform_device *pdev; +}; + +struct sdw_intel_ctx { + int count; + struct sdw_link_data *links; +}; + +static int sdw_intel_cleanup_pdev(struct sdw_intel_ctx *ctx) +{ + struct sdw_link_data *link = ctx->links; + int i; + + if (!link) + return 0; + + for (i = 0; i < ctx->count; i++) { + if (link->pdev) + platform_device_unregister(link->pdev); + link++; + } + + kfree(ctx->links); + ctx->links = NULL; + + return 0; +} + +static struct sdw_intel_ctx +*sdw_intel_add_controller(struct sdw_intel_res *res) +{ + struct platform_device_info pdevinfo; + struct platform_device *pdev; + struct sdw_link_data *link; + struct sdw_intel_ctx *ctx; + struct acpi_device *adev; + int ret, i; + u8 count; + u32 caps; + + if (acpi_bus_get_device(res->handle, &adev)) + return NULL; + + /* Found controller, find links supported */ + count = 0; + ret = fwnode_property_read_u8_array(acpi_fwnode_handle(adev), + "mipi-sdw-master-count", &count, 1); + + /* Don't fail on error, continue and use hw value */ + if (ret) { + dev_err(&adev->dev, + "Failed to read mipi-sdw-master-count: %d\n", ret); + count = SDW_MAX_LINKS; + } + + /* Check SNDWLCAP.LCOUNT */ + caps = ioread32(res->mmio_base + SDW_SHIM_BASE + SDW_SHIM_LCAP); + + /* Check HW supported vs property value and use min of two */ + count = min_t(u8, caps, count); + + /* Check count is within bounds */ + if (count > SDW_MAX_LINKS) { + dev_err(&adev->dev, "Link count %d exceeds max %d\n", + count, SDW_MAX_LINKS); + return NULL; + } + + dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count); + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + + ctx->count = count; + ctx->links = kcalloc(ctx->count, sizeof(*ctx->links), GFP_KERNEL); + if (!ctx->links) + goto link_err; + + link = ctx->links; + + /* Create SDW Master devices */ + for (i = 0; i < count; i++) { + + link->res.irq = res->irq; + link->res.registers = res->mmio_base + SDW_LINK_BASE + + (SDW_LINK_SIZE * i); + link->res.shim = res->mmio_base + SDW_SHIM_BASE; + link->res.alh = res->mmio_base + SDW_ALH_BASE; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + + pdevinfo.parent = res->parent; + pdevinfo.name = "int-sdw"; + pdevinfo.id = i; + pdevinfo.fwnode = acpi_fwnode_handle(adev); + pdevinfo.data = &link->res; + pdevinfo.size_data = sizeof(link->res); + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + dev_err(&adev->dev, + "platform device creation failed: %ld\n", + PTR_ERR(pdev)); + goto pdev_err; + } + + link->pdev = pdev; + link++; + } + + return ctx; + +pdev_err: + sdw_intel_cleanup_pdev(ctx); +link_err: + kfree(ctx); + return NULL; +} + +static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level, + void *cdata, void **return_value) +{ + struct sdw_intel_res *res = cdata; + struct acpi_device *adev; + + if (acpi_bus_get_device(handle, &adev)) { + dev_err(&adev->dev, "Couldn't find ACPI handle\n"); + return AE_NOT_FOUND; + } + + res->handle = handle; + return AE_OK; +} + +/** + * sdw_intel_init() - SoundWire Intel init routine + * @parent_handle: ACPI parent handle + * @res: resource data + * + * This scans the namespace and creates SoundWire link controller devices + * based on the info queried. + */ +void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res) +{ + acpi_status status; + + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, + parent_handle, 1, + sdw_intel_acpi_cb, + NULL, res, NULL); + if (ACPI_FAILURE(status)) + return NULL; + + return sdw_intel_add_controller(res); +} +EXPORT_SYMBOL(sdw_intel_init); + +/** + * sdw_intel_exit() - SoundWire Intel exit + * @arg: callback context + * + * Delete the controller instances created and cleanup + */ +void sdw_intel_exit(void *arg) +{ + struct sdw_intel_ctx *ctx = arg; + + sdw_intel_cleanup_pdev(ctx); + kfree(ctx); +} +EXPORT_SYMBOL(sdw_intel_exit); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Intel Soundwire Init Library"); diff --git a/include/linux/soundwire/sdw_intel.h b/include/linux/soundwire/sdw_intel.h index 370cc52d8d1e..4b37528f592d 100644 --- a/include/linux/soundwire/sdw_intel.h +++ b/include/linux/soundwire/sdw_intel.h @@ -18,4 +18,7 @@ struct sdw_intel_res { struct device *parent; };
+void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res); +void sdw_intel_exit(void *arg); + #endif
Add the SoundWire subsystem maintainer entry with details and Sanyog and me as maintainers.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- MAINTAINERS | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS index aa71ab52fd76..351fd3d87153 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12695,6 +12695,16 @@ F: Documentation/sound/alsa/soc/ F: sound/soc/ F: include/sound/soc*
+SOUNDWIRE SUBSYSTEM +M: Vinod Koul vinod.koul@intel.com +M: Sanyog Kale sanyog.r.kale@intel.com +T: git git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git +L: linux-kernel@vger.kernel.org +S: Supported +F: Documentation/driver-api/soundwire/ +F: drivers/soundwire/ +F: include/linux/soundwire/ + SP2 MEDIA DRIVER M: Olli Salonen olli.salonen@iki.fi L: linux-media@vger.kernel.org
On 12/1/17 3:56 AM, Vinod Koul wrote:
This patch series adds a new SoundWire subsystem which implements a new MIPI bus protocol 'SoundWire'.
Sorry for the late feedback Vinod and team.
Overall the code looks very good to me and aligned with the MIPI specs, there are only a couple of points that were added (or some code not removed) during the code cleanups, e.g. - device12..14 should not be used - device_property handling (master/controller confusion?) - error cases on transfers - spec race condition on interrupt clear (not bad but to be discussed further) - some comments and code nit-picks
This is starting to get into second-order reviews really, which shows this is becoming quite mature. And for the record this work isn't just scratching the surface, it helped identify a couple of documentation issues in the MIPI specs which will lead to clarifications in future revisions.
Looking forward to a v5 ;-)
Thanks, -Pierre
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.
This series adds SoundWire Bus, IO transfers, DisCo (Discovery and Configuration) sysfs interface, regmap and Documentation summary
This patch series is also available on git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git topic/patch_v4
v3: https://lkml.org/lkml/2017/11/30/160 v2: https://lkml.org/lkml/2017/11/10/216 v1: https://lkml.org/lkml/2017/10/18/1030 RFC: https://lkml.org/lkml/2016/10/21/395
Changes in v4:
- Remove text licenses and add SPDX tags only with C99 style comments
- make bus_type code as GPL v2.0 only
Changes in v3:
- Update the kernel-doc styles and fix included headers for files
- handle dev_pm_domain_attach() for defered probe
- remove OF placeholders
- change regmap license to GPLv2 only
Changes in v2:
- move documentation into driver-api and do rst conversion
- fix documentation comments
- add SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) to all source files
- rework the transfer logic and paging logic as commented on v1
- remove dummy sysfs fns
- registration checks and fixes
- remove slave check for regamp as that turned superfluous
- remove depends SoundWire symbol
- make modalias api arg const
- use bitmap for tracking assigned
- add counter for report present tracking
todo: add the dt-bindings
Sanyog Kale (4): Documentation: Add SoundWire summary soundwire: Add SoundWire MIPI defined registers soundwire: Add Slave status handling helpers soundwire: cdns: Add sdw_master_ops and IO transfer support
Vinod Koul (11): soundwire: Add SoundWire bus type soundwire: Add Master registration soundwire: Add MIPI DisCo property helpers soundwire: Add IO transfer regmap: Add SoundWire bus support soundwire: Add slave status handling soundwire: Add sysfs for SoundWire DisCo properties soundwire: cdns: Add cadence library soundwire: intel: Add Intel Master driver soundwire: intel: Add Intel init module MAINTAINERS: Add SoundWire entry
Documentation/driver-api/index.rst | 1 + Documentation/driver-api/soundwire/index.rst | 15 + Documentation/driver-api/soundwire/summary.rst | 205 ++++++ MAINTAINERS | 10 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/base/regmap/Kconfig | 4 + drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-sdw.c | 92 +++ drivers/soundwire/Kconfig | 37 + drivers/soundwire/Makefile | 18 + drivers/soundwire/bus.c | 958 +++++++++++++++++++++++++ drivers/soundwire/bus.h | 74 ++ drivers/soundwire/bus_type.c | 193 +++++ drivers/soundwire/cadence_master.c | 749 +++++++++++++++++++ drivers/soundwire/cadence_master.h | 48 ++ drivers/soundwire/intel.c | 347 +++++++++ drivers/soundwire/intel.h | 23 + drivers/soundwire/intel_init.c | 198 +++++ drivers/soundwire/mipi_disco.c | 374 ++++++++++ drivers/soundwire/slave.c | 115 +++ drivers/soundwire/sysfs.c | 343 +++++++++ include/linux/mod_devicetable.h | 6 + include/linux/regmap.h | 37 + include/linux/soundwire/sdw.h | 487 +++++++++++++ include/linux/soundwire/sdw_intel.h | 24 + include/linux/soundwire/sdw_registers.h | 194 +++++ include/linux/soundwire/sdw_type.h | 19 + scripts/mod/devicetable-offsets.c | 4 + scripts/mod/file2alias.c | 15 + 30 files changed, 4594 insertions(+) create mode 100644 Documentation/driver-api/soundwire/index.rst create mode 100644 Documentation/driver-api/soundwire/summary.rst create mode 100644 drivers/base/regmap/regmap-sdw.c create mode 100644 drivers/soundwire/Kconfig create mode 100644 drivers/soundwire/Makefile create mode 100644 drivers/soundwire/bus.c create mode 100644 drivers/soundwire/bus.h create mode 100644 drivers/soundwire/bus_type.c create mode 100644 drivers/soundwire/cadence_master.c create mode 100644 drivers/soundwire/cadence_master.h create mode 100644 drivers/soundwire/intel.c create mode 100644 drivers/soundwire/intel.h create mode 100644 drivers/soundwire/intel_init.c create mode 100644 drivers/soundwire/mipi_disco.c create mode 100644 drivers/soundwire/slave.c create mode 100644 drivers/soundwire/sysfs.c create mode 100644 include/linux/soundwire/sdw.h create mode 100644 include/linux/soundwire/sdw_intel.h create mode 100644 include/linux/soundwire/sdw_registers.h create mode 100644 include/linux/soundwire/sdw_type.h
On Fri, Dec 01, 2017 at 06:24:10PM -0600, Pierre-Louis Bossart wrote:
On 12/1/17 3:56 AM, Vinod Koul wrote:
This patch series adds a new SoundWire subsystem which implements a new MIPI bus protocol 'SoundWire'.
Sorry for the late feedback Vinod and team.
Overall the code looks very good to me and aligned with the MIPI specs, there are only a couple of points that were added (or some code not removed) during the code cleanups, e.g.
- device12..14 should not be used
- device_property handling (master/controller confusion?)
- error cases on transfers
- spec race condition on interrupt clear (not bad but to be discussed
further)
- some comments and code nit-picks
This is starting to get into second-order reviews really, which shows this is becoming quite mature. And for the record this work isn't just scratching the surface, it helped identify a couple of documentation issues in the MIPI specs which will lead to clarifications in future revisions.
Thanks for the good word, most the stuff you pointed needs a very detailed review, so many thaks for spending time on this one.
Looking forward to a v5 ;-)
Yeah should be there in next few days..
participants (2)
-
Pierre-Louis Bossart
-
Vinod Koul