SLIMbus (Serial Low Power Interchip Media Bus) is a specification
developed by MIPI (Mobile Industry Processor Interface) alliance.
SLIMbus is a 2-wire implementation, which is used to communicate with
peripheral components like audio. Commonly used digital audio
interfaces such as I2S, PCM are intended for point-to-point connection
between application processor and single audio device and support one
or two channels. Adding more channels or functions is difficult
without increasing number of bus structures and hence pin count.
In parallel to audio channels, control buses such as I2C are typically
used for low-bandwidth control tasks.
SLIMbus replaces many digital audio buses and control buses by
providing flexible and dynamic bus-bandwidth between data-functions
and control-functions.
The framework supports message APIs, channel scheduling for SLIMbus.
Message APIs are used for status/control type of communication with a
device. Data Channel APIs are used for setting data channels between
SLIMbus devices.
Framework supports multiple busses (1 controller per bus) and multiple
clients/slave devices per controller.
Signed-off-by: Sagar Dharia <[email protected]>
---
Documentation/slimbus/slimbus-framework.txt | 319 +++
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/of/of_slimbus.c | 89 +
drivers/slimbus/Kconfig | 10 +
drivers/slimbus/Makefile | 4 +
drivers/slimbus/slimbus.c | 3050 +++++++++++++++++++++++++++
include/linux/mod_devicetable.h | 7 +
include/linux/of_slimbus.h | 34 +
include/linux/slimbus/slimbus.h | 1072 ++++++++++
10 files changed, 4588 insertions(+), 0 deletions(-)
create mode 100644 Documentation/slimbus/slimbus-framework.txt
create mode 100644 drivers/of/of_slimbus.c
create mode 100644 drivers/slimbus/Kconfig
create mode 100644 drivers/slimbus/Makefile
create mode 100644 drivers/slimbus/slimbus.c
create mode 100644 include/linux/of_slimbus.h
create mode 100644 include/linux/slimbus/slimbus.h
diff --git a/Documentation/slimbus/slimbus-framework.txt b/Documentation/slimbus/slimbus-framework.txt
new file mode 100644
index 0000000..8c27597
--- /dev/null
+++ b/Documentation/slimbus/slimbus-framework.txt
@@ -0,0 +1,319 @@
+Introduction
+============
+
+Slimbus (Serial Low Power Interchip Media Bus) is a specification developed by
+MIPI (Mobile Industry Processor Interface) alliance. Slimbus is a 2-wire
+implementation, which is used to communicate with peripheral components like
+audio. Commonly used digital audio interfaces such as I2S and PCM are intended
+for point-to-point connection between application processor and single audio
+device and support one or two channels. Adding more channels or functions is
+difficult without increasing number of bus structures and hence pin count.
+In parallel to audio channels, control buses such as I2C are typically used for
+low-bandwidth control tasks.
+
+Slimbus replaces many digital audio buses and control buses by providing
+flexible and dynamic bus-bandwidth between data-functions and control-functions.
+
+
+
+Hardware Description
+====================
+
+Slimbus is a 2-wire multi-drop interface and uses TDM frame structure.
+The devices communicate over a shared bus using predefined transport protocols.
+There are 2 different type of transports.
+
+The message transport is used for various control functions such as bus
+management, configuration and status updates. Messages are seen by all devices
+and the messages can be of unicast and broadcast type. E.g. reading/writing
+device specific values is typically a unicast message. A data channel
+reconfiguration sequence is announced to all devices using a broadcast message.
+
+A data transport is used for data-transfer between 2 Slimbus devices. Data
+transport uses dedicated ports on the device. This data-transfer is not seen
+by all devices.
+
+Slimbus specification has different types of device classifications based on
+their capabilities. A manager device is responsible for enumeration,
+configuration, and dynamic channel allocation. Every bus has 1 active manager.
+A framer device is responsible for driving the clock line and placing
+information on the data line. Multiple framers may exist on a bus and a manager
+is responsible for doing framer-handoffs.
+(Not supported in the SW driver right now).
+
+Per specification, Slimbus uses "clock gears" to do power management based on
+current frequency and bandwidth requirements. There are 10 clock gears and each
+gear changes the Slimbus frequency to be twice its previous gear.
+
+A generic device is a device providing Slimbus functionality and typically has
+ports for data-channel support. Its registers are mapped as 'value elements'
+so that they can be written/read using Slimbus message transport for
+exchanging control/status type of information. Generic devices are also referred
+to as 'clients' in this document from here onward.
+
+Each device has a 6-byte enumeration-address and the manager assigns every
+device with a 1-byte logical address after the devices report present on the
+bus.
+
+Slimbus uses TDM framing at the physical layer and has a variable number of
+frames used for messaging and data-channels. The TDM framing is out of scope
+of this document since clients cannot directly access or interfere with it.
+
+
+
+Software Description:
+====================
+
+Initialization/Bringup:
+-----------------------
+Each bus has one active manager and that is represented by slim_controller
+data structure. The controller is probed during initial kernel bring-up and it
+is responsible to probe other "Generic Devices" and "Framer Devices". All
+devices report present (Broadcast message) after they are probed and
+the manager stores their enumeration addresses and assigns logical addresses.
+Clients get their logical address by calling a public API in case they don't get
+an interrupt after receiving the message.
+
+There are 3 ways in which clients can be added to the bus.
+Register-board-info:
+--------------------
+Here, clients are added using logic similar to I2C. (There is one controller per
+Slimbus who does active-manager duties. Each controller has a bus-number and
+the clients are populated in boards file on that controller). This way, it is
+more target-independent. In addition, clients can be added to the controller
+using the add_device API. This is useful if the client is to be added later
+(as a module). It is also useful if each slave can have multiple generic
+devices but only one driver. The generic device (slave) driver can then
+add more devices per its capability and still have 1 driver.
+DeviceTree:
+-----------
+All children of a controller's DeviceTree node are added as clients of that
+controller. The children should populate enumeration address field so that the
+device to driver binding can happen using enumeration address.
+Report-Present:
+---------------
+According to Slimbus spec, every device sends a report present message when it
+comes up. Slimbus driver framework will check if this device is already listed
+using register-board-info, or DeviceTree method. If not, a new device will be
+created and matched to a driver using enumeration address matching.
+
+Framers are also added per numbered controller and they are expected to
+initialize/enable clock related hardware in their probe. It may be common to
+have 1 driver for manager (controller) and framer. In that case, a separate
+framer device and/or driver is not needed. Framer handover can be used by
+the controller to change the framer per Slimbus spec, but the capability is not
+currently implemented in this framework yet.
+
+Message-based APIs:
+-------------------
+As described earlier, control and status information can be exchanged on
+Slimbus. A Slimbus client device driver can write/read control registers using
+the message-based APIs for doing elemental accesses. Per Slimbus specification,
+there are two type of elements in every device: Value based (values can be
+read/written) and information based (bit-wise information can be set/cleared).
+A Slimbus slave device driver can call these APIs. This typically replaces I2C
+type of control/status transaction. The framework makes sanity checks based on
+Slimbus specification and then calls a controller specific message transfer
+function pointer. The sanity checks make sure that accesses to reserved-address
+space are not allowed, or the size of message isn't out-of-bounds per the
+specification.
+
+Messages are queued in a work-queue (per controller). The Slimbus specification
+has a transaction identification number (TID) for each transaction expecting
+a response. The transaction message is kept per controller until response is
+received, or write-acknowledgement is received.
+
+A client can request to reserve certain bandwidth for messaging if it
+anticipates heavy messaging-traffic on the bus. The Slimbus framework tries to
+reserve the messaging bandwidth and may decide to change clock frequency
+(clock gear) based on existing data-channels on the bus and current frequency,
+and pre-existing reserved-message bandwidth. Reserving bandwidth will fail if
+the clock gear is already maximum and pre-existing data channels and the newly
+requested reserved bandwidth cannot be satisfied.
+
+Data Channel based APIs:
+------------------------
+Data channels can be setup between 2 devices using their data ports. Per
+Slimbus specification, there can be multiple sinks and one source per one data
+channel. Each data channel is uniquely defined by its location in the TDM frame.
+Every manager device also typically has many ports and they are used for data
+transfer between software and the device. Ports have different configuration and
+request types. For each half-duplex port, 2 adjacent physical ports are located.
+
+A port can have source or sink flow. A port has events/errors associated with
+it. Typically, a client requests ports on manager and slave devices. That
+causes a Slimbus specification message to be sent to the device (connect-source
+or connect-sink). Once port allocation is done, the client requests for channel
+to be allocated using the ports it has. Data channel has different protocols
+(such as isochronous, soft-isochronous, asynchronous) based on the clock at
+which Slimbus runs and the channel bandwidth requirements. Clients specify rate
+and the protocol to allocate a channel. Clients then call transfer on the port
+with the input/output vectors and specify completion structure to be notified.
+Clients then call channel activation API to activate the channel.
+
+Channels with common parameters can benefit from grouping. Grouping channels
+helps to schedule/deactive a set of channels at the same time (e.g., 5.1 audio).
+The framework signals completion when the buffers are done and it's up to the
+client to supply more buffers and/or deactivate the channel.
+Completion signal is also used to communicate port errors to the client.
+It's up to the client to act according to the error. Activation/Deactivation
+can be done on pre-existing channels. Clients can call deallocation APIs on
+channels and/or ports when they no longer need the resources.
+
+Clients use "commit" flag in the control channel APIs to group their requests.
+This avoids sending the expensive reconfiguration sequence on the bus too
+frequently. Once the "commit" flag is set, all channel-requests pending on that
+client are acted upon (active-channels are scheduled, remove-channels are
+removed from scheduling, etc.)
+
+Scheduling of channels: Controller and/or framework will call API to reschedule
+TDM framing as it is deemed necessary. This is internally implemented by the
+framework based on current data-channel and message bandwidth requirements.
+Controller may also change scheduling of already active-channels to use TDM
+effectively.
+
+Multiple sinks per channel and channel-port association: Clients can use the
+query-channel API to get a particular channel number. This is useful if a
+particular channel is mapped to a particular port on the device.
+Also, when multiple sinks are used per channel, those sink ports can be
+connected from different clients to this mutually agreed-upon broadcast channel
+number. Data channels keep track of how many clients have connected to this
+channel. Per slimbus specification, a define-channel/content sequence is sent
+per port-connection (everytime a client connects to port(s) and activates the
+channel). Reference counting makes sure that the channel is removed only when
+all the clients have indicated removal of this channel. If source is removed
+but sink is not, then slimbus specification indicates that the sink will keep
+receiving 0's (mute). Also, if sink is removed but source is not, then the data
+on that channel is just not routed to any port (ignored). This helps the sink
+and source port connections to happen in any sequence.
+
+
+
+Design
+======
+Bus-type:
+---------
+Slimbus is represented as a bus and different types of devices listed above hang
+on it. The bus registers with Linux kernel in early stages of kernel bringup.
+Every Slimbus typically has one manager device, one framer device, and multiple
+generic devices (clients). When a device is added/registered, Slimbus matches a
+driver (registered earlier) and calls the driver probe.
+
+Message transactions:
+---------------------
+For faster and efficient transaction management, Linux framework uses tid for
+every transaction irrespective of whether it expects a response. This helps in
+O(1) search of the transaction when it's complete, or its response is received,
+or it needs to be resent due to collision/NACK. Once a transaction is complete,
+its resources are just marked unused and not deleted. A transaction is allocated
+if an unused transaction is not found. Per Slimbus specification, TID is 8 bits
+long and cannot exceed 256, so this won't cause a lot of memory usage and
+allocation for transactions won't be required to be done too often.
+
+Ports and channels:
+-------------------
+Manager-ports and channels are allocated as arrays. This helps faster access
+based on channel/port number. Typically the number of ports and channels are
+limited and having this as a list won't gain significant memory advantage.
+
+For effective scheduling, the 2 most commonly used frequency-channels (4KHz and
+12KHz) are grouped in 2 sorted lists. The lists only have pointers (to channel
+elements allocated above).
+
+
+
+Power Management
+================
+Slimbus hardware uses only 2 wires for managing multiple devices, multiple
+channels and control/status messaging. This is power efficient. In addition,
+clock gears are used to go to lower clock gears when low-bandwidth messaging
+is used and/or limited data channels are used.
+
+Clock-pause:
+------------
+Slimbus specification provides clock-pause support where the controller informs
+all devices on the bus that the bus will no longer be clocked.
+This sequence can be initiated by a controller when there is no activity on the
+bus. Since this clock-pause sequence consists of sending 3 messages on the bus,
+it's recommended that controllers use auto-suspend feature of runtime-PM to
+avoid thrashing the bus between clock-pause and subsequent wakeup.
+
+
+
+SMP/multi-core
+==============
+Board initialization list for listing devices, framers use a board-specific
+mutex. This mutex is common to all controllers.
+
+Controllers are listed in the framework using idr list. The list is protected by
+a common mutex.
+
+Controller data structures (channel, port, address-table, transaction-id table)
+are protected using a controller mutex.
+
+Reconfiguration mutex per controller is used to make sure that only one
+reconfiguration sequence is in progress per controller.
+
+Client structure has a mutex to protect the pending data channel request-lists
+per client.
+
+
+
+Security
+========
+None at this point.
+
+
+
+Interface
+=========
+There is no plan of exporting slimbus functionality to user space right now.
+
+Kernel space APIs:
+include/linux/slimbus/slimbus.h
+-----------------------
+
+
+
+Driver parameters
+=================
+None.
+
+
+
+Config options
+==============
+Slimbus in drivers/Kconfig
+
+
+
+Dependencies
+============
+None.
+
+
+
+User space utilities
+====================
+None at this point. No user space access is expected for Slimbus at this point.
+If need be, a client can be implemented to manage all user-space accesses
+using ioctl.
+
+
+
+Known issues
+============
+None.
+
+
+
+To do
+=====
+Framer handoff is not supported right now. This is required when a bus has
+multiple framers and the active framer needs to be decided on the fly.
+
+Specific logical address request is not supported right now. Client drivers may
+request for specific logical address and that may change logical addresses of
+other preexisting devices on the bus.
+
+Debugfs will be used to debug channel management and bandwidth usage in future.
diff --git a/drivers/Kconfig b/drivers/Kconfig
index d236aef..c331293 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -52,6 +52,8 @@ source "drivers/spi/Kconfig"
source "drivers/hsi/Kconfig"
+source "drivers/slimbus/Kconfig"
+
source "drivers/pps/Kconfig"
source "drivers/ptp/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 95952c8..c47934b 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_TARGET_CORE) += target/
obj-$(CONFIG_MTD) += mtd/
obj-$(CONFIG_SPI) += spi/
obj-y += hsi/
+obj-$(CONFIG_SLIMBUS) += slimbus/
obj-y += net/
obj-$(CONFIG_ATM) += atm/
obj-$(CONFIG_FUSION) += message/
diff --git a/drivers/of/of_slimbus.c b/drivers/of/of_slimbus.c
new file mode 100644
index 0000000..c38efdc
--- /dev/null
+++ b/drivers/of/of_slimbus.c
@@ -0,0 +1,89 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/* OF helpers for SLIMbus */
+#include <linux/slimbus/slimbus.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_slimbus.h>
+
+int of_register_slim_devices(struct slim_controller *ctrl)
+{
+ struct device_node *node;
+ struct slim_boardinfo *binfo = NULL;
+ int n = 0;
+ int ret = 0;
+
+ if (!ctrl->dev.of_node)
+ return -EINVAL;
+
+ for_each_child_of_node(ctrl->dev.of_node, node) {
+ struct property *prop;
+ u8 *ea;
+ struct slim_device *slim;
+ char *name;
+ prop = of_find_property(node, "elemental-addr", NULL);
+ if (!prop || prop->length != 6) {
+ dev_err(&ctrl->dev, "of_slim: invalid E-addr");
+ continue;
+ }
+ ea = (u8 *)prop->value;
+ name = kzalloc(SLIMBUS_NAME_SIZE, GFP_KERNEL);
+ if (!name) {
+ dev_err(&ctrl->dev, "of_slim: out of memory");
+ ret = -ENOMEM;
+ goto of_slim_err;
+ }
+ if (of_modalias_node(node, name, SLIMBUS_NAME_SIZE) < 0) {
+ dev_err(&ctrl->dev, "of_slim: modalias failure on %s\n",
+ node->full_name);
+ kfree(name);
+ continue;
+ }
+ slim = kzalloc(sizeof(struct slim_device), GFP_KERNEL);
+ if (!slim) {
+ dev_err(&ctrl->dev, "of_slim: out of memory");
+ ret = -ENOMEM;
+ kfree(name);
+ goto of_slim_err;
+ }
+ slim->e_addr.manf_id = (u16)(ea[5] << 8) | ea[4];
+ slim->e_addr.prod_code = (u16)(ea[3] << 8) | ea[2];
+ slim->e_addr.dev_index = ea[1];
+ slim->e_addr.instance = ea[0];
+
+
+ binfo = krealloc(binfo, (n + 1) * sizeof(struct slim_boardinfo),
+ GFP_KERNEL);
+ if (!binfo) {
+ dev_err(&ctrl->dev, "out of memory");
+ kfree(name);
+ kfree(slim);
+ return -ENOMEM;
+ }
+ slim->name = name;
+ binfo[n].bus_num = ctrl->nr;
+ binfo[n].slim_slave = slim;
+ n++;
+ }
+ return slim_register_board_info(binfo, n);
+of_slim_err:
+ n--;
+ while (n >= 0) {
+ kfree(binfo[n].slim_slave->name);
+ kfree(binfo[n].slim_slave);
+ }
+ kfree(binfo);
+ return ret;
+}
diff --git a/drivers/slimbus/Kconfig b/drivers/slimbus/Kconfig
new file mode 100644
index 0000000..aff2eb9
--- /dev/null
+++ b/drivers/slimbus/Kconfig
@@ -0,0 +1,10 @@
+#
+# SLIMBUS driver configuration
+#
+menuconfig SLIMBUS
+ bool "Slimbus support"
+ depends on HAS_IOMEM
+ help
+ Slimbus is standard interface between baseband and
+ application processors and peripheral components in mobile
+ terminals.
diff --git a/drivers/slimbus/Makefile b/drivers/slimbus/Makefile
new file mode 100644
index 0000000..05f53bc
--- /dev/null
+++ b/drivers/slimbus/Makefile
@@ -0,0 +1,4 @@
+#
+# Makefile for kernel slimbus framework.
+#
+obj-$(CONFIG_SLIMBUS) += slimbus.o
diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c
new file mode 100644
index 0000000..6d20241
--- /dev/null
+++ b/drivers/slimbus/slimbus.c
@@ -0,0 +1,3050 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/completion.h>
+#include <linux/idr.h>
+#include <linux/pm_runtime.h>
+#include <linux/slimbus/slimbus.h>
+
+#define SLIM_PORT_HDL(la, f, p) ((la)<<24 | (f) << 16 | (p))
+
+#define SLIM_HDL_TO_LA(hdl) ((u32)((hdl) & 0xFF000000) >> 24)
+#define SLIM_HDL_TO_FLOW(hdl) (((u32)(hdl) & 0xFF0000) >> 16)
+#define SLIM_HDL_TO_PORT(hdl) ((u32)(hdl) & 0xFF)
+
+#define SLIM_HDL_TO_CHIDX(hdl) ((u16)(hdl) & 0xFF)
+
+#define SLIM_SLAVE_PORT(p, la) (((la)<<16) | (p))
+#define SLIM_MGR_PORT(p) ((0xFF << 16) | (p))
+#define SLIM_LA_MANAGER 0xFF
+
+#define SLIM_START_GRP (1 << 8)
+#define SLIM_END_GRP (1 << 9)
+
+#define SLIM_MAX_INTR_COEFF_3 (SLIM_SL_PER_SUPERFRAME/3)
+#define SLIM_MAX_INTR_COEFF_1 SLIM_SL_PER_SUPERFRAME
+
+static DEFINE_MUTEX(slim_lock);
+static DEFINE_IDR(ctrl_idr);
+static struct device_type slim_dev_type;
+static struct device_type slim_ctrl_type;
+
+static int slim_compare_eaddr(struct slim_eaddr *a, struct slim_eaddr *b)
+{
+ if (a->manf_id == b->manf_id && a->prod_code == b->prod_code &&
+ a->dev_index == b->dev_index && a->instance == b->instance)
+ return 0;
+ return -EIO;
+}
+static const struct slim_device_id *slim_match(const struct slim_device_id *id,
+ const struct slim_device *slim_dev)
+{
+ while (id->manf_id != 0 || id->prod_code != 0) {
+ if (id->manf_id == slim_dev->e_addr.manf_id &&
+ id->prod_code == slim_dev->e_addr.prod_code &&
+ id->dev_index == slim_dev->e_addr.dev_index)
+ return id;
+ id++;
+ }
+ return NULL;
+}
+
+static int slim_device_match(struct device *dev, struct device_driver *driver)
+{
+ struct slim_device *slim_dev;
+ struct slim_driver *drv = to_slim_driver(driver);
+
+ if (dev->type == &slim_dev_type)
+ slim_dev = to_slim_device(dev);
+ else
+ return 0;
+ if (drv->id_table)
+ return slim_match(drv->id_table, slim_dev) != NULL;
+
+ return 0;
+}
+
+static int slim_device_probe(struct device *dev)
+{
+ struct slim_device *slim_dev;
+ struct slim_driver *driver;
+ int status = 0;
+
+ if (dev->type == &slim_dev_type)
+ slim_dev = to_slim_device(dev);
+ else
+ return 0;
+
+ driver = to_slim_driver(dev->driver);
+ if (!driver->id_table)
+ return -ENODEV;
+ slim_dev->driver = driver;
+
+ if (driver->probe)
+ status = driver->probe(slim_dev);
+ if (status)
+ slim_dev->driver = NULL;
+ return status;
+}
+
+static int slim_device_remove(struct device *dev)
+{
+ struct slim_device *slim_dev;
+ struct slim_driver *driver;
+ int status = 0;
+
+ if (dev->type == &slim_dev_type)
+ slim_dev = to_slim_device(dev);
+ else
+ return 0;
+
+ driver = to_slim_driver(dev->driver);
+ if (driver->remove)
+ status = driver->remove(slim_dev);
+
+ if (status == 0)
+ slim_dev->driver = NULL;
+ return status;
+}
+
+static void slim_device_shutdown(struct device *dev)
+{
+ struct slim_device *slim_dev;
+ struct slim_driver *driver;
+
+ if (dev->type == &slim_dev_type)
+ slim_dev = to_slim_device(dev);
+ else
+ return;
+
+ driver = to_slim_driver(dev->driver);
+ if (driver->shutdown)
+ driver->shutdown(slim_dev);
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int slim_pm_suspend(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ if (pm)
+ return pm_generic_suspend(dev);
+ else
+ return 0;
+}
+
+static int slim_pm_resume(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ if (pm)
+ return pm_generic_resume(dev);
+ else
+ return 0;
+}
+
+#else
+#define slim_pm_suspend NULL
+#define slim_pm_resume NULL
+#endif
+
+static const struct dev_pm_ops slimbus_pm = {
+ .suspend = slim_pm_suspend,
+ .resume = slim_pm_resume,
+ SET_RUNTIME_PM_OPS(
+ pm_generic_suspend,
+ pm_generic_resume,
+ pm_generic_runtime_idle
+ )
+};
+struct bus_type slimbus_type = {
+ .name = "slimbus",
+ .match = slim_device_match,
+ .probe = slim_device_probe,
+ .remove = slim_device_remove,
+ .shutdown = slim_device_shutdown,
+ .pm = &slimbus_pm,
+};
+EXPORT_SYMBOL_GPL(slimbus_type);
+
+static void __exit slimbus_exit(void)
+{
+ bus_unregister(&slimbus_type);
+}
+
+static int __init slimbus_init(void)
+{
+ return bus_register(&slimbus_type);
+}
+postcore_initcall(slimbus_init);
+module_exit(slimbus_exit);
+/*
+ * slim_driver_register: Client driver registration with slimbus
+ * @drv:Client driver to be associated with client-device.
+ * This API will register the client driver with the slimbus
+ * It is called from the driver's module-init function.
+ */
+int slim_driver_register(struct slim_driver *drv)
+{
+ drv->driver.bus = &slimbus_type;
+
+ return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(slim_driver_register);
+
+#define slim_ctrl_attr_gr NULL
+
+static void slim_ctrl_release(struct device *dev)
+{
+ struct slim_controller *ctrl = to_slim_controller(dev);
+
+ complete(&ctrl->dev_released);
+}
+
+static struct device_type slim_ctrl_type = {
+ .groups = slim_ctrl_attr_gr,
+ .release = slim_ctrl_release,
+};
+
+static struct slim_controller *slim_ctrl_get(struct slim_controller *ctrl)
+{
+ if (!ctrl || !get_device(&ctrl->dev))
+ return NULL;
+
+ return ctrl;
+}
+
+static void slim_ctrl_put(struct slim_controller *ctrl)
+{
+ if (ctrl)
+ put_device(&ctrl->dev);
+}
+
+#define slim_device_attr_gr NULL
+#define slim_device_uevent NULL
+static void slim_dev_release(struct device *dev)
+{
+ struct slim_device *sbdev = to_slim_device(dev);
+ slim_ctrl_put(sbdev->ctrl);
+}
+
+static struct device_type slim_dev_type = {
+ .groups = slim_device_attr_gr,
+ .uevent = slim_device_uevent,
+ .release = slim_dev_release,
+};
+
+/*
+ * slim_add_device: Add a new device without register board info.
+ * @ctrl: Controller to which this device is to be added to.
+ * Called when device doesn't have an explicit client-driver to be probed, or
+ * the client-driver is a module installed dynamically.
+ */
+int slim_add_device(struct slim_controller *ctrl, struct slim_device *sbdev)
+{
+ int ret = 0;
+ int i;
+
+ sbdev->dev.bus = &slimbus_type;
+ sbdev->dev.parent = ctrl->dev.parent;
+ sbdev->dev.type = &slim_dev_type;
+ sbdev->ctrl = ctrl;
+ slim_ctrl_get(ctrl);
+ if (!sbdev->name) {
+ sbdev->name = kzalloc(SLIMBUS_NAME_SIZE, GFP_KERNEL);
+ if (!sbdev->name)
+ return -ENOMEM;
+ snprintf(sbdev->name, SLIMBUS_NAME_SIZE, "0x%x:0x%x:0x%x:0x%x",
+ sbdev->e_addr.manf_id, sbdev->e_addr.prod_code,
+ sbdev->e_addr.dev_index,
+ sbdev->e_addr.instance);
+ }
+ pr_err("adding device:%s", sbdev->name);
+ dev_set_name(&sbdev->dev, "%s", sbdev->name);
+ /* probe slave on this controller */
+ ret = device_register(&sbdev->dev);
+
+ if (ret) {
+ slim_ctrl_put(ctrl);
+ return ret;
+ }
+
+ mutex_init(&sbdev->sldev_reconf);
+ init_completion(&sbdev->wait_enum);
+ /* see if the device is already enumerated */
+ mutex_lock(&ctrl->m_ctrl);
+ for (i = 0; i < ctrl->num_dev; i++) {
+ if (ctrl->addrt[i].listed &&
+ (slim_compare_eaddr(&ctrl->addrt[i].eaddr,
+ &sbdev->e_addr) == 0)) {
+ sbdev->laddr = ctrl->addrt[i].laddr;
+ sbdev->enumerated = true;
+ complete(&sbdev->wait_enum);
+ }
+ }
+ mutex_unlock(&ctrl->m_ctrl);
+
+ INIT_LIST_HEAD(&sbdev->mark_define);
+ INIT_LIST_HEAD(&sbdev->mark_suspend);
+ INIT_LIST_HEAD(&sbdev->mark_removal);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(slim_add_device);
+
+struct sbi_boardinfo {
+ struct list_head list;
+ struct slim_boardinfo board_info;
+};
+
+static LIST_HEAD(board_list);
+static LIST_HEAD(slim_ctrl_list);
+static DEFINE_MUTEX(board_lock);
+
+/*
+ * slim_query_device: Query and get handle to a device.
+ * @ctrl: Controller on which this device will be added/queried
+ * @e_addr: Elemental address of the device to be queried
+ * Returns pointer to a device if it has already reported. Creates a new
+ * device and returns pointer to it if the device has not yet enumerated.
+ */
+struct slim_device *slim_query_device(struct slim_controller *ctrl,
+ struct slim_eaddr *e_addr)
+{
+ struct slim_device *slim = NULL;
+ struct sbi_boardinfo *bi;
+ mutex_lock(&board_lock);
+ list_for_each_entry(bi, &board_list, list) {
+ if (bi->board_info.bus_num != ctrl->nr)
+ continue;
+ if (slim_compare_eaddr(&bi->board_info.slim_slave->e_addr,
+ e_addr) == 0) {
+ slim = bi->board_info.slim_slave;
+ break;
+ }
+ }
+ if (!slim) {
+ slim = kzalloc(sizeof(struct slim_device), GFP_KERNEL);
+ if (!slim)
+ goto err_dev_add;
+ slim->e_addr = *e_addr;
+ bi = kzalloc(sizeof(struct sbi_boardinfo), GFP_KERNEL);
+ if (!bi) {
+ kfree(slim);
+ slim = NULL;
+ goto err_dev_add;
+ }
+ bi->board_info.bus_num = ctrl->nr;
+ bi->board_info.slim_slave = slim;
+ if (slim_add_device(ctrl, slim) != 0) {
+ kfree(bi);
+ kfree(slim);
+ slim = NULL;
+ goto err_dev_add;
+ }
+ list_add_tail(&bi->list, &board_list);
+ }
+err_dev_add:
+ mutex_unlock(&board_lock);
+ return slim;
+}
+EXPORT_SYMBOL_GPL(slim_query_device);
+
+/* If controller is not present, only add to boards list */
+static void slim_match_ctrl_to_boardinfo(struct slim_controller *ctrl,
+ struct slim_boardinfo *bi)
+{
+ int ret;
+ int i;
+ if (ctrl->nr != bi->bus_num)
+ return;
+
+ /* If the device is in logical address table then it's already added */
+ mutex_lock(&ctrl->m_ctrl);
+ for (i = 0; i < ctrl->num_dev; i++) {
+ if (ctrl->addrt[i].listed &&
+ (slim_compare_eaddr(&ctrl->addrt[i].eaddr,
+ &bi->slim_slave->e_addr) == 0)) {
+ mutex_unlock(&ctrl->m_ctrl);
+ return;
+ }
+ }
+ mutex_unlock(&ctrl->m_ctrl);
+ ret = slim_add_device(ctrl, bi->slim_slave);
+ if (ret != 0)
+ dev_err(ctrl->dev.parent, "can't create new device for %s\n",
+ bi->slim_slave->name);
+}
+
+/*
+ * slim_register_board_info: Board-initialization routine.
+ * @info: List of all devices on all controllers present on the board.
+ * @n: number of entries.
+ * API enumerates respective devices on corresponding controller.
+ * Called from board-init function.
+ */
+int slim_register_board_info(struct slim_boardinfo const *info, unsigned n)
+{
+ struct sbi_boardinfo *bi;
+ int i;
+
+ bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
+ if (!bi)
+ return -ENOMEM;
+
+ for (i = 0; i < n; i++, bi++, info++) {
+ struct slim_controller *ctrl;
+
+ memcpy(&bi->board_info, info, sizeof(*info));
+ mutex_lock(&board_lock);
+ list_add_tail(&bi->list, &board_list);
+ list_for_each_entry(ctrl, &slim_ctrl_list, list)
+ slim_match_ctrl_to_boardinfo(ctrl, &bi->board_info);
+ mutex_unlock(&board_lock);
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(slim_register_board_info);
+
+static int slim_register_controller(struct slim_controller *ctrl)
+{
+ int ret = 0;
+ struct sbi_boardinfo *bi;
+
+ /* Can't register until after driver model init */
+ if (WARN_ON(!slimbus_type.p)) {
+ ret = -EAGAIN;
+ goto out_list;
+ }
+
+ dev_set_name(&ctrl->dev, "sb-%d", ctrl->nr);
+ ctrl->dev.bus = &slimbus_type;
+ ctrl->dev.type = &slim_ctrl_type;
+ ctrl->num_dev = 0;
+ if (!ctrl->min_cg)
+ ctrl->min_cg = SLIM_MIN_CLK_GEAR;
+ if (!ctrl->max_cg)
+ ctrl->max_cg = SLIM_MAX_CLK_GEAR;
+ mutex_init(&ctrl->m_ctrl);
+ mutex_init(&ctrl->sched.m_reconf);
+ ret = device_register(&ctrl->dev);
+ if (ret)
+ goto out_list;
+
+ dev_dbg(&ctrl->dev, "Bus [%s] registered:dev:%x\n", ctrl->name,
+ (u32)&ctrl->dev);
+
+ if (ctrl->nports) {
+ ctrl->ports = kzalloc(ctrl->nports * sizeof(struct slim_port),
+ GFP_KERNEL);
+ if (!ctrl->ports) {
+ ret = -ENOMEM;
+ goto err_port_failed;
+ }
+ }
+ if (ctrl->nchans) {
+ ctrl->chans = kzalloc(ctrl->nchans * sizeof(struct slim_ich),
+ GFP_KERNEL);
+ if (!ctrl->chans) {
+ ret = -ENOMEM;
+ goto err_chan_failed;
+ }
+
+ ctrl->sched.chc1 =
+ kzalloc(ctrl->nchans * sizeof(struct slim_ich *),
+ GFP_KERNEL);
+ if (!ctrl->sched.chc1) {
+ kfree(ctrl->chans);
+ ret = -ENOMEM;
+ goto err_chan_failed;
+ }
+ ctrl->sched.chc3 =
+ kzalloc(ctrl->nchans * sizeof(struct slim_ich *),
+ GFP_KERNEL);
+ if (!ctrl->sched.chc3) {
+ kfree(ctrl->sched.chc1);
+ kfree(ctrl->chans);
+ ret = -ENOMEM;
+ goto err_chan_failed;
+ }
+ }
+#ifdef DEBUG
+ ctrl->sched.slots = kzalloc(SLIM_SL_PER_SUPERFRAME, GFP_KERNEL);
+#endif
+ init_completion(&ctrl->pause_comp);
+ /*
+ * If devices on a controller were registered before controller,
+ * this will make sure that they get probed now that controller is up
+ */
+ mutex_lock(&board_lock);
+ list_add_tail(&ctrl->list, &slim_ctrl_list);
+ list_for_each_entry(bi, &board_list, list)
+ slim_match_ctrl_to_boardinfo(ctrl, &bi->board_info);
+ mutex_unlock(&board_lock);
+
+ return 0;
+
+err_chan_failed:
+ kfree(ctrl->ports);
+err_port_failed:
+ device_unregister(&ctrl->dev);
+out_list:
+ mutex_lock(&slim_lock);
+ idr_remove(&ctrl_idr, ctrl->nr);
+ mutex_unlock(&slim_lock);
+ return ret;
+}
+
+/* slim_remove_device: Remove the effect of slim_add_device() */
+void slim_remove_device(struct slim_device *sbdev)
+{
+ device_unregister(&sbdev->dev);
+}
+EXPORT_SYMBOL_GPL(slim_remove_device);
+
+static void slim_ctrl_remove_device(struct slim_controller *ctrl,
+ struct slim_boardinfo *bi)
+{
+ if (ctrl->nr == bi->bus_num)
+ slim_remove_device(bi->slim_slave);
+}
+
+/*
+ * slim_del_controller: Controller tear-down.
+ * Controller added with the above API is teared down using this API.
+ */
+int slim_del_controller(struct slim_controller *ctrl)
+{
+ struct slim_controller *found;
+ struct sbi_boardinfo *bi;
+
+ /* First make sure that this bus was added */
+ mutex_lock(&slim_lock);
+ found = idr_find(&ctrl_idr, ctrl->nr);
+ mutex_unlock(&slim_lock);
+ if (found != ctrl)
+ return -EINVAL;
+
+ /* Remove all clients */
+ mutex_lock(&board_lock);
+ list_for_each_entry(bi, &board_list, list)
+ slim_ctrl_remove_device(ctrl, &bi->board_info);
+ mutex_unlock(&board_lock);
+
+ init_completion(&ctrl->dev_released);
+ device_unregister(&ctrl->dev);
+
+ wait_for_completion(&ctrl->dev_released);
+ list_del(&ctrl->list);
+ /* free bus id */
+ mutex_lock(&slim_lock);
+ idr_remove(&ctrl_idr, ctrl->nr);
+ mutex_unlock(&slim_lock);
+
+ kfree(ctrl->sched.chc1);
+ kfree(ctrl->sched.chc3);
+#ifdef DEBUG
+ kfree(ctrl->sched.slots);
+#endif
+ kfree(ctrl->chans);
+ kfree(ctrl->ports);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(slim_del_controller);
+
+/*
+ * slim_add_numbered_controller: Controller bring-up.
+ * @ctrl: Controller to be registered.
+ * A controller is registered with the framework using this API. ctrl->nr is the
+ * desired number with which slimbus framework registers the controller.
+ * Function will return -EBUSY if the number is in use.
+ */
+int slim_add_numbered_controller(struct slim_controller *ctrl)
+{
+ int id;
+ int status;
+
+ if (ctrl->nr & ~MAX_ID_MASK)
+ return -EINVAL;
+
+retry:
+ if (idr_pre_get(&ctrl_idr, GFP_KERNEL) == 0)
+ return -ENOMEM;
+
+ mutex_lock(&slim_lock);
+ status = idr_get_new_above(&ctrl_idr, ctrl, ctrl->nr, &id);
+ if (status == 0 && id != ctrl->nr) {
+ status = -EAGAIN;
+ idr_remove(&ctrl_idr, id);
+ }
+ mutex_unlock(&slim_lock);
+ if (status == -EAGAIN)
+ goto retry;
+
+ if (status == 0)
+ status = slim_register_controller(ctrl);
+ return status;
+}
+EXPORT_SYMBOL_GPL(slim_add_numbered_controller);
+
+/*
+ * slim_msg_response: Deliver Message response received from a device to the
+ * framework.
+ * @ctrl: Controller handle
+ * @reply: Reply received from the device
+ * @len: Length of the reply
+ * @tid: Transaction ID received with which framework can associate reply.
+ * Called by controller to inform framework about the response received.
+ * This helps in making the API asynchronous, and controller-driver doesn't need
+ * to manage 1 more table other than the one managed by framework mapping TID
+ * with buffers
+ */
+void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len)
+{
+ int i;
+ struct slim_msg_txn *txn;
+
+ mutex_lock(&ctrl->m_ctrl);
+ txn = ctrl->txnt[tid];
+ if (txn == NULL) {
+ dev_err(&ctrl->dev, "Got response to invalid TID:%d, len:%d",
+ tid, len);
+ mutex_unlock(&ctrl->m_ctrl);
+ return;
+ }
+ for (i = 0; i < len; i++)
+ txn->rbuf[i] = reply[i];
+ if (txn->comp)
+ complete(txn->comp);
+ ctrl->txnt[tid] = NULL;
+ mutex_unlock(&ctrl->m_ctrl);
+ kfree(txn);
+}
+EXPORT_SYMBOL_GPL(slim_msg_response);
+
+static int slim_processtxn(struct slim_controller *ctrl, u8 dt, u16 mc, u16 ec,
+ u8 mt, u8 *rbuf, const u8 *wbuf, u8 len, u8 mlen,
+ struct completion *comp, u8 la, u8 *tid)
+{
+ u8 i = 0;
+ int ret = 0;
+ struct slim_msg_txn *txn = kmalloc(sizeof(struct slim_msg_txn),
+ GFP_KERNEL);
+ if (!txn)
+ return -ENOMEM;
+ if (tid) {
+ mutex_lock(&ctrl->m_ctrl);
+ for (i = 0; i < ctrl->last_tid; i++) {
+ if (ctrl->txnt[i] == NULL)
+ break;
+ }
+ if (i >= ctrl->last_tid) {
+ if (ctrl->last_tid == 255) {
+ mutex_unlock(&ctrl->m_ctrl);
+ kfree(txn);
+ return -ENOMEM;
+ }
+ ctrl->txnt = krealloc(ctrl->txnt,
+ (i + 1) * sizeof(struct slim_msg_txn *),
+ GFP_KERNEL);
+ if (!ctrl->txnt) {
+ mutex_unlock(&ctrl->m_ctrl);
+ kfree(txn);
+ return -ENOMEM;
+ }
+ ctrl->last_tid++;
+ }
+ ctrl->txnt[i] = txn;
+ mutex_unlock(&ctrl->m_ctrl);
+ txn->tid = i;
+ *tid = i;
+ }
+ txn->mc = mc;
+ txn->mt = mt;
+ txn->dt = dt;
+ txn->ec = ec;
+ txn->la = la;
+ txn->rbuf = rbuf;
+ txn->wbuf = wbuf;
+ txn->rl = mlen;
+ txn->len = len;
+ txn->comp = comp;
+
+ ret = ctrl->xfer_msg(ctrl, txn);
+ if (!tid)
+ kfree(txn);
+ return ret;
+}
+
+static int ctrl_getaddr_entry(struct slim_controller *ctrl,
+ struct slim_eaddr *eaddr, u8 *entry)
+{
+ u8 i;
+ for (i = 0; i < ctrl->num_dev; i++) {
+ if (ctrl->addrt[i].valid &&
+ slim_compare_eaddr(&ctrl->addrt[i].eaddr, eaddr)
+ == 0) {
+ *entry = i;
+ return 0;
+ }
+ }
+ return -ENXIO;
+}
+
+/*
+ * slim_assign_laddr: Assign logical address to a device enumerated.
+ * @ctrl: Controller with which device is enumerated.
+ * @e_addr: Elemental address of the device.
+ * @laddr: Return logical address.
+ * Called by controller in response to REPORT_PRESENT. Framework will assign
+ * a logical address to this enumeration address.
+ * Function returns -EXFULL to indicate that all logical addresses are already
+ * taken.
+ */
+int slim_assign_laddr(struct slim_controller *ctrl, struct slim_eaddr *e_addr,
+ u8 *laddr)
+{
+ int ret;
+ u8 i = 0;
+ bool exists = false;
+ struct slim_device *slim;
+ mutex_lock(&ctrl->m_ctrl);
+ /* already assigned */
+ if (ctrl_getaddr_entry(ctrl, e_addr, &i) == 0) {
+ *laddr = ctrl->addrt[i].laddr;
+ exists = true;
+ } else {
+ if (ctrl->num_dev >= 254) {
+ ret = -EXFULL;
+ goto ret_assigned_laddr;
+ }
+ for (i = 0; i < ctrl->num_dev; i++) {
+ if (ctrl->addrt[i].valid == false)
+ break;
+ }
+ if (i == ctrl->num_dev) {
+ ctrl->addrt = krealloc(ctrl->addrt,
+ (ctrl->num_dev + 1) *
+ sizeof(struct slim_addrt),
+ GFP_KERNEL);
+ if (!ctrl->addrt) {
+ ret = -ENOMEM;
+ goto ret_assigned_laddr;
+ }
+ ctrl->num_dev++;
+ }
+ ctrl->addrt[i].eaddr = *e_addr;
+ ctrl->addrt[i].valid = true;
+ /* Preferred address is index into table */
+ *laddr = i;
+ }
+
+ ret = ctrl->set_laddr(ctrl, &ctrl->addrt[i].eaddr, laddr);
+ if (ret) {
+ ctrl->addrt[i].valid = false;
+ goto ret_assigned_laddr;
+ }
+ ctrl->addrt[i].laddr = *laddr;
+
+ dev_dbg(&ctrl->dev, "setting slimbus l-addr:%x, ea:%x,%x,%x,%x\n",
+ *laddr, e_addr->manf_id, e_addr->prod_code,
+ e_addr->dev_index, e_addr->instance);
+ret_assigned_laddr:
+ mutex_unlock(&ctrl->m_ctrl);
+ if (exists || ret)
+ return ret;
+ /*
+ * Add this device to list of devices on this controller if it's
+ * not already present
+ */
+ slim = slim_query_device(ctrl, e_addr);
+ if (!slim)
+ ret = -ENOMEM;
+ else {
+ mutex_lock(&ctrl->m_ctrl);
+ ctrl->addrt[i].listed = true;
+ mutex_unlock(&ctrl->m_ctrl);
+ slim->laddr = *laddr;
+ slim->enumerated = true;
+ complete(&slim->wait_enum);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_assign_laddr);
+
+/*
+ * slim_get_logical_addr: Return the logical address of a slimbus device.
+ * @sb: client handle requesting the adddress.
+ * @e_addr: Elemental address of the device.
+ * @laddr: output buffer to store the address
+ * context: can sleep
+ * -EINVAL is returned in case of invalid parameters, and -ENXIO is returned if
+ * the device with this elemental address is not found.
+ */
+int slim_get_logical_addr(struct slim_device *sb, struct slim_eaddr *e_addr,
+ u8 *laddr)
+{
+ int ret = 0;
+ u8 entry = 0;
+ struct slim_controller *ctrl = sb->ctrl;
+ if (!ctrl || !laddr || !e_addr)
+ return -EINVAL;
+ mutex_lock(&ctrl->m_ctrl);
+ ret = ctrl_getaddr_entry(ctrl, e_addr, &entry);
+ if (!ret)
+ *laddr = ctrl->addrt[entry].laddr;
+ mutex_unlock(&ctrl->m_ctrl);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_get_logical_addr);
+
+static int slim_ele_access_sanity(struct slim_ele_access *msg, int oper,
+ u8 *rbuf, const u8 *wbuf, u8 len)
+{
+ if (!msg || msg->num_bytes > 16 || msg->start_offset + len > 0xC00)
+ return -EINVAL;
+ switch (oper) {
+ case SLIM_MSG_MC_REQUEST_VALUE:
+ case SLIM_MSG_MC_REQUEST_INFORMATION:
+ if (rbuf == NULL)
+ return -EINVAL;
+ return 0;
+ case SLIM_MSG_MC_CHANGE_VALUE:
+ case SLIM_MSG_MC_CLEAR_INFORMATION:
+ if (wbuf == NULL)
+ return -EINVAL;
+ return 0;
+ case SLIM_MSG_MC_REQUEST_CHANGE_VALUE:
+ case SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION:
+ if (rbuf == NULL || wbuf == NULL)
+ return -EINVAL;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static u16 slim_slicecodefromsize(u32 req)
+{
+ static const u8 codetosize[8] = {1, 2, 3, 4, 6, 8, 12, 16};
+ if (req >= ARRAY_SIZE(codetosize))
+ return 0;
+ else
+ return codetosize[req];
+}
+
+static u16 slim_slicesize(u32 code)
+{
+ static const u8 sizetocode[16] = {0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6,
+ 6, 6, 7};
+ if (code == 0)
+ code = 1;
+ if (code > ARRAY_SIZE(sizetocode))
+ code = 16;
+ return sizetocode[code - 1];
+}
+
+
+/* Message APIs Unicast message APIs used by slimbus slave drivers */
+
+/*
+ * Message API access routines.
+ * @sb: client handle requesting elemental message reads, writes.
+ * @msg: Input structure for start-offset, number of bytes to read.
+ * @rbuf: data buffer to be filled with values read.
+ * @len: data buffer size
+ * @wbuf: data buffer containing value/information to be written
+ * context: can sleep
+ * Returns:
+ * -EINVAL: Invalid parameters
+ * -ETIMEDOUT: If controller could not complete the request. This may happen if
+ * the bus lines are not clocked, controller is not powered-on, slave with
+ * given address is not enumerated/responding.
+ */
+int slim_request_val_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *buf, u8 len)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_VALUE, buf,
+ NULL, len);
+}
+EXPORT_SYMBOL_GPL(slim_request_val_element);
+
+int slim_request_inf_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *buf, u8 len)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_INFORMATION,
+ buf, NULL, len);
+}
+EXPORT_SYMBOL_GPL(slim_request_inf_element);
+
+int slim_change_val_element(struct slim_device *sb, struct slim_ele_access *msg,
+ const u8 *buf, u8 len)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_CHANGE_VALUE, NULL, buf,
+ len);
+}
+EXPORT_SYMBOL_GPL(slim_change_val_element);
+
+int slim_clear_inf_element(struct slim_device *sb, struct slim_ele_access *msg,
+ u8 *buf, u8 len)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_CLEAR_INFORMATION, NULL,
+ buf, len);
+}
+EXPORT_SYMBOL_GPL(slim_clear_inf_element);
+
+int slim_request_change_val_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *rbuf,
+ const u8 *wbuf, u8 len)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_CHANGE_VALUE,
+ rbuf, wbuf, len);
+}
+EXPORT_SYMBOL_GPL(slim_request_change_val_element);
+
+int slim_request_clear_inf_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *rbuf,
+ const u8 *wbuf, u8 len)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg,
+ SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION,
+ rbuf, wbuf, len);
+}
+EXPORT_SYMBOL_GPL(slim_request_clear_inf_element);
+
+/*
+ * Broadcast message API:
+ * call this API directly with sbdev = NULL.
+ * For broadcast reads, make sure that buffers are big-enough to incorporate
+ * replies from all logical addresses.
+ * All controllers may not support broadcast
+ */
+int slim_xfer_msg(struct slim_controller *ctrl, struct slim_device *sbdev,
+ struct slim_ele_access *msg, u16 mc, u8 *rbuf,
+ const u8 *wbuf, u8 len)
+{
+ DECLARE_COMPLETION_ONSTACK(complete);
+ int ret;
+ u16 sl, cur;
+ u16 ec;
+ u8 tid, mlen = 6;
+
+ /*if (!sbdev->enumerated)
+ return -ENXIO;*/
+ ret = slim_ele_access_sanity(msg, mc, rbuf, wbuf, len);
+ if (ret)
+ goto xfer_err;
+
+ sl = slim_slicesize(len);
+ dev_dbg(&ctrl->dev, "SB xfer msg:os:%x, len:%d, MC:%x, sl:%x\n",
+ msg->start_offset, len, mc, sl);
+
+ cur = slim_slicecodefromsize(sl);
+ ec = ((sl | (1 << 3)) | ((msg->start_offset & 0xFFF) << 4));
+
+ if (wbuf)
+ mlen += len;
+ if (rbuf) {
+ mlen++;
+ if (!msg->comp)
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR,
+ mc, ec, SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen,
+ &complete, sbdev->laddr, &tid);
+ else
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR,
+ mc, ec, SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen,
+ msg->comp, sbdev->laddr, &tid);
+ /* sync read */
+ if (!ret && !msg->comp) {
+ ret = wait_for_completion_timeout(&complete, HZ);
+ if (!ret) {
+ struct slim_msg_txn *txn;
+ dev_err(&ctrl->dev, "slimbus Read timed out");
+ mutex_lock(&ctrl->m_ctrl);
+ txn = ctrl->txnt[tid];
+ /* Invalidate the transaction */
+ ctrl->txnt[tid] = NULL;
+ mutex_unlock(&ctrl->m_ctrl);
+ kfree(txn);
+ ret = -ETIMEDOUT;
+ } else
+ ret = 0;
+ } else if (ret < 0 && !msg->comp) {
+ struct slim_msg_txn *txn;
+ dev_err(&ctrl->dev, "slimbus Read error");
+ mutex_lock(&ctrl->m_ctrl);
+ txn = ctrl->txnt[tid];
+ /* Invalidate the transaction */
+ ctrl->txnt[tid] = NULL;
+ mutex_unlock(&ctrl->m_ctrl);
+ kfree(txn);
+ }
+
+ } else
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, ec,
+ SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen,
+ NULL, sbdev->laddr, NULL);
+xfer_err:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_xfer_msg);
+
+/*
+ * slim_alloc_mgrports: Allocate port on manager side.
+ * @sb: device/client handle.
+ * @req: Port request type.
+ * @nports: Number of ports requested
+ * @rh: output buffer to store the port handles
+ * @hsz: size of buffer storing handles
+ * context: can sleep
+ * This port will be typically used by SW. e.g. client driver wants to receive
+ * some data from audio codec HW using a data channel.
+ * Port allocated using this API will be used to receive the data.
+ * If half-duplex ports are requested, two adjacent ports are allocated for
+ * 1 half-duplex port. So the handle-buffer size should be twice the number
+ * of half-duplex ports to be allocated.
+ * -EDQUOT is returned if all ports are in use.
+ */
+int slim_alloc_mgrports(struct slim_device *sb, enum slim_port_req req,
+ int nports, u32 *rh, int hsz)
+{
+ int i, j;
+ int ret = -EINVAL;
+ int nphysp = nports;
+ struct slim_controller *ctrl = sb->ctrl;
+
+ if (!rh || !ctrl)
+ return -EINVAL;
+ if (req == SLIM_REQ_HALF_DUP)
+ nphysp *= 2;
+ if (hsz/sizeof(u32) < nphysp)
+ return -EINVAL;
+ mutex_lock(&ctrl->m_ctrl);
+
+ for (i = 0; i < ctrl->nports; i++) {
+ bool multiok = true;
+ if (ctrl->ports[i].state != SLIM_P_FREE)
+ continue;
+ /* Start half duplex channel at even port */
+ if (req == SLIM_REQ_HALF_DUP && (i % 2))
+ continue;
+ /* Allocate ports contiguously for multi-ch */
+ if (ctrl->nports < (i + nphysp)) {
+ i = ctrl->nports;
+ break;
+ }
+ if (req == SLIM_REQ_MULTI_CH) {
+ multiok = true;
+ for (j = i; j < i + nphysp; j++) {
+ if (ctrl->ports[j].state != SLIM_P_FREE) {
+ multiok = false;
+ break;
+ }
+ }
+ if (!multiok)
+ continue;
+ }
+ break;
+ }
+ if (i >= ctrl->nports)
+ ret = -EDQUOT;
+ for (j = i; j < i + nphysp; j++) {
+ ctrl->ports[j].state = SLIM_P_UNCFG;
+ ctrl->ports[j].req = req;
+ if (req == SLIM_REQ_HALF_DUP && (j % 2))
+ ctrl->ports[j].flow = SLIM_SINK;
+ else
+ ctrl->ports[j].flow = SLIM_SRC;
+ ret = ctrl->config_port(ctrl, j);
+ if (ret) {
+ for (; j >= i; j--)
+ ctrl->ports[j].state = SLIM_P_FREE;
+ goto alloc_err;
+ }
+ *rh++ = SLIM_PORT_HDL(SLIM_LA_MANAGER, 0, j);
+ }
+alloc_err:
+ mutex_unlock(&ctrl->m_ctrl);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_alloc_mgrports);
+
+/* Deallocate the port(s) allocated using the API above */
+int slim_dealloc_mgrports(struct slim_device *sb, u32 *hdl, int nports)
+{
+ int i;
+ struct slim_controller *ctrl = sb->ctrl;
+
+ if (!ctrl || !hdl)
+ return -EINVAL;
+
+ mutex_lock(&ctrl->m_ctrl);
+
+ for (i = 0; i < nports; i++) {
+ u8 pn;
+ pn = SLIM_HDL_TO_PORT(hdl[i]);
+ if (ctrl->ports[pn].state == SLIM_P_CFG) {
+ int j;
+ dev_err(&ctrl->dev, "Can't dealloc connected port:%d",
+ i);
+ for (j = i - 1; j >= 0; j--) {
+ pn = SLIM_HDL_TO_PORT(hdl[j]);
+ ctrl->ports[pn].state = SLIM_P_UNCFG;
+ }
+ mutex_unlock(&ctrl->m_ctrl);
+ return -EISCONN;
+ }
+ ctrl->ports[pn].state = SLIM_P_FREE;
+ }
+ mutex_unlock(&ctrl->m_ctrl);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(slim_dealloc_mgrports);
+
+/*
+ * slim_get_slaveport: Get slave port handle
+ * @la: slave device logical address.
+ * @idx: port index at slave
+ * @rh: return handle
+ * @flw: Flow type (source or destination)
+ * This API only returns a slave port's representation as expected by slimbus
+ * driver. This port is not managed by the slimbus driver. Caller is expected
+ * to have visibility of this port since it's a device-port.
+ */
+int slim_get_slaveport(u8 la, int idx, u32 *rh, enum slim_port_flow flw)
+{
+ if (rh == NULL)
+ return -EINVAL;
+ *rh = SLIM_PORT_HDL(la, flw, idx);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(slim_get_slaveport);
+
+static int connect_port_ch(struct slim_controller *ctrl, u8 ch, u32 ph,
+ enum slim_port_flow flow)
+{
+ int ret;
+ u16 mc;
+ u8 buf[2];
+ u32 la = SLIM_HDL_TO_LA(ph);
+ u8 pn = (u8)SLIM_HDL_TO_PORT(ph);
+
+ if (flow == SLIM_SRC)
+ mc = SLIM_MSG_MC_CONNECT_SOURCE;
+ else
+ mc = SLIM_MSG_MC_CONNECT_SINK;
+ buf[0] = pn;
+ buf[1] = ctrl->chans[ch].chan;
+ if (la == SLIM_LA_MANAGER)
+ ctrl->ports[pn].flow = flow;
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, 0,
+ SLIM_MSG_MT_CORE, NULL, buf, 2, 6, NULL, la,
+ NULL);
+ if (!ret && la == SLIM_LA_MANAGER)
+ ctrl->ports[pn].state = SLIM_P_CFG;
+ return ret;
+}
+
+static int disconnect_port_ch(struct slim_controller *ctrl, u32 ph)
+{
+ int ret;
+ u16 mc;
+ u32 la = SLIM_HDL_TO_LA(ph);
+ u8 pn = (u8)SLIM_HDL_TO_PORT(ph);
+
+ mc = SLIM_MSG_MC_DISCONNECT_PORT;
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, 0,
+ SLIM_MSG_MT_CORE, NULL, &pn, 1, 5,
+ NULL, la, NULL);
+ if (ret)
+ return ret;
+ if (la == SLIM_LA_MANAGER)
+ ctrl->ports[pn].state = SLIM_P_UNCFG;
+ return 0;
+}
+
+/*
+ * slim_connect_src: Connect source port to channel.
+ * @sb: client handle
+ * @srch: source handle to be connected to this channel
+ * @chanh: Channel with which the ports need to be associated with.
+ * Per slimbus specification, a channel may have 1 source port.
+ * Channel specified in chanh needs to be allocated first.
+ * Returns -EALREADY if source is already configured for this channel.
+ * Returns -ENOTCONN if channel is not allocated
+ */
+int slim_connect_src(struct slim_device *sb, u32 srch, u16 chanh)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ int ret;
+ u8 chan = SLIM_HDL_TO_CHIDX(chanh);
+ struct slim_ich *slc = &ctrl->chans[chan];
+ enum slim_port_flow flow = SLIM_HDL_TO_FLOW(srch);
+
+ if (flow != SLIM_SRC)
+ return -EINVAL;
+
+ mutex_lock(&ctrl->m_ctrl);
+
+ if (slc->state == SLIM_CH_FREE) {
+ ret = -ENOTCONN;
+ goto connect_src_err;
+ }
+ /*
+ * Once channel is removed, its ports can be considered disconnected
+ * So its ports can be reassigned. Source port is zeroed
+ * when channel is deallocated.
+ */
+ if (slc->srch) {
+ ret = -EALREADY;
+ goto connect_src_err;
+ }
+
+ ret = connect_port_ch(ctrl, chan, srch, SLIM_SRC);
+
+ if (!ret)
+ slc->srch = srch;
+
+connect_src_err:
+ mutex_unlock(&ctrl->m_ctrl);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_connect_src);
+
+/*
+ * slim_connect_sink: Connect sink port(s) to channel.
+ * @sb: client handle
+ * @sinkh: sink handle(s) to be connected to this channel
+ * @nsink: number of sinks
+ * @chanh: Channel with which the ports need to be associated with.
+ * Per slimbus specification, a channel may have multiple sink-ports.
+ * Channel specified in chanh needs to be allocated first.
+ * Returns -EALREADY if sink is already configured for this channel.
+ * Returns -ENOTCONN if channel is not allocated
+ */
+int slim_connect_sink(struct slim_device *sb, u32 *sinkh, int nsink, u16 chanh)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ int j;
+ int ret = 0;
+ u8 chan = SLIM_HDL_TO_CHIDX(chanh);
+ struct slim_ich *slc = &ctrl->chans[chan];
+
+ if (!sinkh || !nsink)
+ return -EINVAL;
+
+ mutex_lock(&ctrl->m_ctrl);
+
+ /*
+ * Once channel is removed, its ports can be considered disconnected
+ * So its ports can be reassigned. Sink ports are freed when channel
+ * is deallocated.
+ */
+ if (slc->state == SLIM_CH_FREE) {
+ ret = -ENOTCONN;
+ goto connect_sink_err;
+ }
+
+ for (j = 0; j < nsink; j++) {
+ enum slim_port_flow flow = SLIM_HDL_TO_FLOW(sinkh[j]);
+ if (flow != SLIM_SINK)
+ ret = -EINVAL;
+ else
+ ret = connect_port_ch(ctrl, chan, sinkh[j], SLIM_SINK);
+ if (ret) {
+ for (j = j - 1; j >= 0; j--)
+ disconnect_port_ch(ctrl, sinkh[j]);
+ goto connect_sink_err;
+ }
+ }
+
+ slc->sinkh = krealloc(slc->sinkh, (sizeof(u32) * (slc->nsink + nsink)),
+ GFP_KERNEL);
+ if (!slc->sinkh) {
+ ret = -ENOMEM;
+ for (j = 0; j < nsink; j++)
+ disconnect_port_ch(ctrl, sinkh[j]);
+ goto connect_sink_err;
+ }
+
+ memcpy(slc->sinkh + slc->nsink, sinkh, (sizeof(u32) * nsink));
+ slc->nsink += nsink;
+
+connect_sink_err:
+ mutex_unlock(&ctrl->m_ctrl);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_connect_sink);
+
+/*
+ * slim_disconnect_ports: Disconnect port(s) from channel
+ * @sb: client handle
+ * @ph: ports to be disconnected
+ * @nph: number of ports.
+ * Disconnects ports from a channel.
+ */
+int slim_disconnect_ports(struct slim_device *sb, u32 *ph, int nph)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ int i;
+
+ mutex_lock(&ctrl->m_ctrl);
+
+ for (i = 0; i < nph; i++)
+ disconnect_port_ch(ctrl, ph[i]);
+ mutex_unlock(&ctrl->m_ctrl);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(slim_disconnect_ports);
+
+/*
+ * slim_port_xfer: Schedule buffer to be transferred/received using port-handle.
+ * @sb: client handle
+ * @ph: port-handle
+ * @iobuf: buffer to be transferred or populated
+ * @len: buffer size.
+ * @comp: completion signal to indicate transfer done or error.
+ * context: can sleep
+ * Returns number of bytes transferred/received if used synchronously.
+ * Will return 0 if used asynchronously.
+ * Client will call slim_port_get_xfer_status to get error and/or number of
+ * bytes transferred if used asynchronously.
+ */
+int slim_port_xfer(struct slim_device *sb, u32 ph, u8 *iobuf, u32 len,
+ struct completion *comp)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ u8 pn = SLIM_HDL_TO_PORT(ph);
+ dev_dbg(&ctrl->dev, "port xfer: num:%d", pn);
+ return ctrl->port_xfer(ctrl, pn, iobuf, len, comp);
+}
+EXPORT_SYMBOL_GPL(slim_port_xfer);
+
+/*
+ * slim_port_get_xfer_status: Poll for port transfers, or get transfer status
+ * after completion is done.
+ * @sb: client handle
+ * @ph: port-handle
+ * @done_buf: return pointer (iobuf from slim_port_xfer) which is processed.
+ * @done_len: Number of bytes transferred.
+ * This can be called when port_xfer complition is signalled.
+ * The API will return port transfer error (underflow/overflow/disconnect)
+ * and/or done_len will reflect number of bytes transferred. Note that
+ * done_len may be valid even if port error (overflow/underflow) has happened.
+ * e.g. If the transfer was scheduled with a few bytes to be transferred and
+ * client has not supplied more data to be transferred, done_len will indicate
+ * number of bytes transferred with underflow error. To avoid frequent underflow
+ * errors, multiple transfers can be queued (e.g. ping-pong buffers) so that
+ * channel has data to be transferred even if client is not ready to transfer
+ * data all the time. done_buf will indicate address of the last buffer
+ * processed from the multiple transfers.
+ */
+enum slim_port_err slim_port_get_xfer_status(struct slim_device *sb, u32 ph,
+ u8 **done_buf, u32 *done_len)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ u8 pn = SLIM_HDL_TO_PORT(ph);
+ u32 la = SLIM_HDL_TO_LA(ph);
+ enum slim_port_err err;
+ dev_dbg(&ctrl->dev, "get status port num:%d", pn);
+ /*
+ * Framework only has insight into ports managed by ported device
+ * used by the manager and not slave
+ */
+ if (la != SLIM_LA_MANAGER) {
+ if (done_buf)
+ *done_buf = NULL;
+ if (done_len)
+ *done_len = 0;
+ return SLIM_P_NOT_OWNED;
+ }
+ err = ctrl->port_xfer_status(ctrl, pn, done_buf, done_len);
+ if (err == SLIM_P_INPROGRESS)
+ err = ctrl->ports[pn].err;
+ return err;
+}
+EXPORT_SYMBOL_GPL(slim_port_get_xfer_status);
+
+static void slim_add_ch(struct slim_controller *ctrl, struct slim_ich *slc)
+{
+ struct slim_ich **arr;
+ int i, j;
+ int *len;
+ int sl = slc->seglen << slc->rootexp;
+ /* Channel is already active and other end is transmitting data */
+ if (slc->state >= SLIM_CH_ACTIVE)
+ return;
+ if (slc->coeff == SLIM_COEFF_1) {
+ arr = ctrl->sched.chc1;
+ len = &ctrl->sched.num_cc1;
+ } else {
+ arr = ctrl->sched.chc3;
+ len = &ctrl->sched.num_cc3;
+ sl *= 3;
+ }
+
+ *len += 1;
+
+ /* Insert the channel based on rootexp and seglen */
+ for (i = 0; i < *len - 1; i++) {
+ /*
+ * Primary key: exp low to high.
+ * Secondary key: seglen: high to low
+ */
+ if ((slc->rootexp > arr[i]->rootexp) ||
+ ((slc->rootexp == arr[i]->rootexp) &&
+ (slc->seglen < arr[i]->seglen)))
+ continue;
+ else
+ break;
+ }
+ for (j = *len - 1; j > i; j--)
+ arr[j] = arr[j - 1];
+ arr[i] = slc;
+ ctrl->sched.usedslots += sl;
+
+ return;
+}
+
+static int slim_remove_ch(struct slim_controller *ctrl, struct slim_ich *slc)
+{
+ struct slim_ich **arr;
+ int i;
+ u32 la, ph;
+ int *len;
+ if (slc->coeff == SLIM_COEFF_1) {
+ arr = ctrl->sched.chc1;
+ len = &ctrl->sched.num_cc1;
+ } else {
+ arr = ctrl->sched.chc3;
+ len = &ctrl->sched.num_cc3;
+ }
+
+ for (i = 0; i < *len; i++) {
+ if (arr[i] == slc)
+ break;
+ }
+ if (i >= *len)
+ return -EXFULL;
+ for (; i < *len - 1; i++)
+ arr[i] = arr[i + 1];
+ *len -= 1;
+ arr[*len] = NULL;
+
+ slc->state = SLIM_CH_ALLOCATED;
+ slc->newintr = 0;
+ slc->newoff = 0;
+ for (i = 0; i < slc->nsink; i++) {
+ ph = slc->sinkh[i];
+ la = SLIM_HDL_TO_LA(ph);
+ /*
+ * For ports managed by manager's ported device, no need to send
+ * disconnect. It is client's responsibility to call disconnect
+ * on ports owned by the slave device
+ */
+ if (la == SLIM_LA_MANAGER)
+ ctrl->ports[SLIM_HDL_TO_PORT(ph)].state = SLIM_P_UNCFG;
+ }
+
+ ph = slc->srch;
+ la = SLIM_HDL_TO_LA(ph);
+ if (la == SLIM_LA_MANAGER)
+ ctrl->ports[SLIM_HDL_TO_PORT(ph)].state = SLIM_P_UNCFG;
+
+ kfree(slc->sinkh);
+ slc->sinkh = NULL;
+ slc->srch = 0;
+ slc->nsink = 0;
+ return 0;
+}
+
+static u32 slim_calc_prrate(struct slim_controller *ctrl, struct slim_ch *prop)
+{
+ u32 rate = 0, rate4k = 0, rate11k = 0;
+ u32 exp = 0;
+ u32 pr = 0;
+ bool exact = true;
+ bool done = false;
+ enum slim_ch_rate ratefam;
+
+ if (prop->prot >= SLIM_PUSH)
+ return 0;
+ if (prop->baser == SLIM_RATE_1HZ) {
+ rate = prop->ratem / 4000;
+ rate4k = rate;
+ if (rate * 4000 == prop->ratem)
+ ratefam = SLIM_RATE_4000HZ;
+ else {
+ rate = prop->ratem / 11025;
+ rate11k = rate;
+ if (rate * 11025 == prop->ratem)
+ ratefam = SLIM_RATE_11025HZ;
+ else
+ ratefam = SLIM_RATE_1HZ;
+ }
+ } else {
+ ratefam = prop->baser;
+ rate = prop->ratem;
+ }
+ if (ratefam == SLIM_RATE_1HZ) {
+ exact = false;
+ if ((rate4k + 1) * 4000 < (rate11k + 1) * 11025) {
+ rate = rate4k + 1;
+ ratefam = SLIM_RATE_4000HZ;
+ } else {
+ rate = rate11k + 1;
+ ratefam = SLIM_RATE_11025HZ;
+ }
+ }
+ /* covert rate to coeff-exp */
+ while (!done) {
+ while ((rate & 0x1) != 0x1) {
+ rate >>= 1;
+ exp++;
+ }
+ if (rate > 3) {
+ /* roundup if not exact */
+ rate++;
+ exact = false;
+ } else
+ done = true;
+ }
+ if (ratefam == SLIM_RATE_4000HZ) {
+ if (rate == 1)
+ pr = 0x10;
+ else {
+ pr = 0;
+ exp++;
+ }
+ } else {
+ pr = 8;
+ exp++;
+ }
+ if (exp <= 7) {
+ pr |= exp;
+ if (exact)
+ pr |= 0x80;
+ } else
+ pr = 0;
+ return pr;
+}
+
+static int slim_nextdefine_ch(struct slim_device *sb, u8 chan)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ u32 chrate = 0;
+ u32 exp = 0;
+ u32 coeff = 0;
+ bool exact = true;
+ bool done = false;
+ int ret = 0;
+ struct slim_ich *slc = &ctrl->chans[chan];
+ struct slim_ch *prop = &slc->prop;
+
+ slc->prrate = slim_calc_prrate(ctrl, prop);
+ dev_dbg(&ctrl->dev, "ch:%d, chan PR rate:%x\n", chan, slc->prrate);
+ if (prop->baser == SLIM_RATE_4000HZ)
+ chrate = 4000 * prop->ratem;
+ else if (prop->baser == SLIM_RATE_11025HZ)
+ chrate = 11025 * prop->ratem;
+ else
+ chrate = prop->ratem;
+ /* max allowed sample freq = 768 seg/frame */
+ if (chrate > 3600000)
+ return -EDQUOT;
+ if (prop->baser == SLIM_RATE_4000HZ &&
+ ctrl->a_framer->superfreq == 4000)
+ coeff = prop->ratem;
+ else if (prop->baser == SLIM_RATE_11025HZ &&
+ ctrl->a_framer->superfreq == 3675)
+ coeff = 3 * prop->ratem;
+ else {
+ u32 tempr = 0;
+ tempr = chrate * SLIM_CL_PER_SUPERFRAME_DIV8;
+ coeff = tempr / ctrl->a_framer->rootfreq;
+ if (coeff * ctrl->a_framer->rootfreq != tempr) {
+ coeff++;
+ exact = false;
+ }
+ }
+
+ /* convert coeff to coeff-exponent */
+ exp = 0;
+ while (!done) {
+ while ((coeff & 0x1) != 0x1) {
+ coeff >>= 1;
+ exp++;
+ }
+ if (coeff > 3) {
+ coeff++;
+ exact = false;
+ } else
+ done = true;
+ }
+ if (prop->prot == SLIM_HARD_ISO && !exact)
+ return -EPROTONOSUPPORT;
+ else if (prop->prot == SLIM_AUTO_ISO) {
+ if (exact)
+ prop->prot = SLIM_HARD_ISO;
+ else {
+ /* Push-Pull not supported for now */
+ return -EPROTONOSUPPORT;
+ }
+ }
+ slc->rootexp = exp;
+ slc->seglen = prop->sampleszbits/SLIM_CL_PER_SL;
+ if (prop->prot != SLIM_HARD_ISO)
+ slc->seglen++;
+ if (prop->prot >= SLIM_EXT_SMPLX)
+ slc->seglen++;
+ /* convert coeff to enum */
+ if (coeff == 1) {
+ if (exp > 9)
+ ret = -EIO;
+ coeff = SLIM_COEFF_1;
+ } else {
+ if (exp > 8)
+ ret = -EIO;
+ coeff = SLIM_COEFF_3;
+ }
+ slc->coeff = coeff;
+
+ return ret;
+}
+
+/*
+ * slim_alloc_ch: Allocate a slimbus channel and return its handle.
+ * @sb: client handle.
+ * @chanh: return channel handle
+ * Slimbus channels are limited to 256 per specification.
+ * -EXFULL is returned if all channels are in use.
+ * Although slimbus specification supports 256 channels, a controller may not
+ * support that many channels.
+ */
+int slim_alloc_ch(struct slim_device *sb, u16 *chanh)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ u16 i;
+
+ if (!ctrl)
+ return -EINVAL;
+ mutex_lock(&ctrl->m_ctrl);
+ for (i = 0; i < ctrl->nchans; i++) {
+ if (ctrl->chans[i].state == SLIM_CH_FREE)
+ break;
+ }
+ if (i >= ctrl->nchans) {
+ mutex_unlock(&ctrl->m_ctrl);
+ return -EXFULL;
+ }
+ *chanh = i;
+ ctrl->chans[i].nextgrp = 0;
+ ctrl->chans[i].state = SLIM_CH_ALLOCATED;
+ ctrl->chans[i].chan = (u8)(ctrl->reserved + i);
+
+ mutex_unlock(&ctrl->m_ctrl);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(slim_alloc_ch);
+
+/*
+ * slim_query_ch: Get reference-counted handle for a channel number. Every
+ * channel is reference counted by upto one as producer and the others as
+ * consumer)
+ * @sb: client handle
+ * @chan: slimbus channel number
+ * @chanh: return channel handle
+ * If request channel number is not in use, it is allocated, and reference
+ * count is set to one. If the channel was was already allocated, this API
+ * will return handle to that channel and reference count is incremented.
+ * -EXFULL is returned if all channels are in use
+ */
+int slim_query_ch(struct slim_device *sb, u8 ch, u16 *chanh)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ u16 i, j;
+ int ret = 0;
+ if (!ctrl || !chanh)
+ return -EINVAL;
+ mutex_lock(&ctrl->m_ctrl);
+ /* start with modulo number */
+ i = ch % ctrl->nchans;
+
+ for (j = 0; j < ctrl->nchans; j++) {
+ if (ctrl->chans[i].chan == ch) {
+ *chanh = i;
+ ctrl->chans[i].ref++;
+ if (ctrl->chans[i].state == SLIM_CH_FREE)
+ ctrl->chans[i].state = SLIM_CH_ALLOCATED;
+ goto query_out;
+ }
+ i = (i + 1) % ctrl->nchans;
+ }
+
+ /* Channel not in table yet */
+ ret = -EXFULL;
+ for (j = 0; j < ctrl->nchans; j++) {
+ if (ctrl->chans[i].state == SLIM_CH_FREE) {
+ ctrl->chans[i].state =
+ SLIM_CH_ALLOCATED;
+ *chanh = i;
+ ctrl->chans[i].ref++;
+ ctrl->chans[i].chan = ch;
+ ctrl->chans[i].nextgrp = 0;
+ ret = 0;
+ break;
+ }
+ i = (i + 1) % ctrl->nchans;
+ }
+query_out:
+ mutex_unlock(&ctrl->m_ctrl);
+ dev_dbg(&ctrl->dev, "query ch:%d,hdl:%d,ref:%d,ret:%d",
+ ch, i, ctrl->chans[i].ref, ret);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_query_ch);
+
+/*
+ * slim_dealloc_ch: Deallocate channel allocated using the API above
+ * -EISCONN is returned if the channel is tried to be deallocated without
+ * being removed first.
+ * -ENOTCONN is returned if deallocation is tried on a channel that's not
+ * allocated.
+ */
+int slim_dealloc_ch(struct slim_device *sb, u16 chanh)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ u8 chan = SLIM_HDL_TO_CHIDX(chanh);
+ struct slim_ich *slc = &ctrl->chans[chan];
+ if (!ctrl)
+ return -EINVAL;
+
+ mutex_lock(&ctrl->m_ctrl);
+ if (slc->state == SLIM_CH_FREE) {
+ mutex_unlock(&ctrl->m_ctrl);
+ return -ENOTCONN;
+ }
+ if (slc->ref > 1) {
+ slc->ref--;
+ mutex_unlock(&ctrl->m_ctrl);
+ dev_dbg(&ctrl->dev, "remove chan:%d,hdl:%d,ref:%d",
+ slc->chan, chanh, slc->ref);
+ return 0;
+ }
+ if (slc->state >= SLIM_CH_PENDING_ACTIVE) {
+ dev_err(&ctrl->dev, "Channel:%d should be removed first", chan);
+ mutex_unlock(&ctrl->m_ctrl);
+ return -EISCONN;
+ }
+ slc->ref--;
+ slc->state = SLIM_CH_FREE;
+ mutex_unlock(&ctrl->m_ctrl);
+ dev_dbg(&ctrl->dev, "remove chan:%d,hdl:%d,ref:%d",
+ slc->chan, chanh, slc->ref);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(slim_dealloc_ch);
+
+/*
+ * slim_get_ch_state: Channel state.
+ * This API returns the channel's state (active, suspended, inactive etc)
+ */
+enum slim_ch_state slim_get_ch_state(struct slim_device *sb, u16 chanh)
+{
+ u8 chan = SLIM_HDL_TO_CHIDX(chanh);
+ struct slim_ich *slc = &sb->ctrl->chans[chan];
+ return slc->state;
+}
+EXPORT_SYMBOL_GPL(slim_get_ch_state);
+
+/*
+ * slim_define_ch: Define a channel.This API defines channel parameters for a
+ * given channel.
+ * @sb: client handle.
+ * @prop: slim_ch structure with channel parameters desired to be used.
+ * @chanh: list of channels to be defined.
+ * @nchan: number of channels in a group (1 if grp is false)
+ * @grp: Are the channels grouped
+ * @grph: return group handle if grouping of channels is desired.
+ * Channels can be grouped if multiple channels use same parameters
+ * (e.g. 5.1 audio has 6 channels with same parameters. They will all be grouped
+ * and given 1 handle for simplicity and avoid repeatedly calling the API)
+ * -EISCONN is returned if channel is already used with different parameters.
+ * -ENXIO is returned if the channel is not yet allocated.
+ */
+int slim_define_ch(struct slim_device *sb, struct slim_ch *prop, u16 *chanh,
+ u8 nchan, bool grp, u16 *grph)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ int i, ret = 0;
+
+ if (!ctrl || !chanh || !prop || !nchan)
+ return -EINVAL;
+ mutex_lock(&ctrl->m_ctrl);
+ for (i = 0; i < nchan; i++) {
+ u8 chan = SLIM_HDL_TO_CHIDX(chanh[i]);
+ struct slim_ich *slc = &ctrl->chans[chan];
+ dev_dbg(&ctrl->dev, "define_ch: ch:%d, state:%d", chan,
+ (int)ctrl->chans[chan].state);
+ if (slc->state < SLIM_CH_ALLOCATED) {
+ ret = -ENXIO;
+ goto err_define_ch;
+ }
+ if (slc->state >= SLIM_CH_DEFINED && slc->ref >= 2) {
+ if (prop->ratem != slc->prop.ratem ||
+ prop->sampleszbits != slc->prop.sampleszbits ||
+ prop->baser != slc->prop.baser) {
+ ret = -EISCONN;
+ goto err_define_ch;
+ }
+ } else if (slc->state > SLIM_CH_DEFINED) {
+ ret = -EISCONN;
+ goto err_define_ch;
+ } else {
+ ctrl->chans[chan].prop = *prop;
+ ret = slim_nextdefine_ch(sb, chan);
+ if (ret)
+ goto err_define_ch;
+ }
+ if (i < (nchan - 1))
+ ctrl->chans[chan].nextgrp = chanh[i + 1];
+ if (i == 0)
+ ctrl->chans[chan].nextgrp |= SLIM_START_GRP;
+ if (i == (nchan - 1))
+ ctrl->chans[chan].nextgrp |= SLIM_END_GRP;
+ }
+
+ if (grp)
+ *grph = chanh[0];
+ for (i = 0; i < nchan; i++) {
+ u8 chan = SLIM_HDL_TO_CHIDX(chanh[i]);
+ struct slim_ich *slc = &ctrl->chans[chan];
+ if (slc->state == SLIM_CH_ALLOCATED)
+ slc->state = SLIM_CH_DEFINED;
+ }
+err_define_ch:
+ dev_dbg(&ctrl->dev, "define_ch: ch:%d, ret:%d", *chanh, ret);
+ mutex_unlock(&ctrl->m_ctrl);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_define_ch);
+
+static u32 getsubfrmcoding(u32 *ctrlw, u32 *subfrml, u32 *msgsl)
+{
+ u32 code = 0;
+ if (*ctrlw == *subfrml) {
+ *ctrlw = 8;
+ *subfrml = 8;
+ *msgsl = SLIM_SL_PER_SUPERFRAME - SLIM_FRM_SLOTS_PER_SUPERFRAME
+ - SLIM_GDE_SLOTS_PER_SUPERFRAME;
+ return 0;
+ }
+ if (*subfrml == 6) {
+ code = 0;
+ *msgsl = 256;
+ } else if (*subfrml == 8) {
+ code = 1;
+ *msgsl = 192;
+ } else if (*subfrml == 24) {
+ code = 2;
+ *msgsl = 64;
+ } else { /* 32 */
+ code = 3;
+ *msgsl = 48;
+ }
+
+ if (*ctrlw < 8) {
+ if (*ctrlw >= 6) {
+ *ctrlw = 6;
+ code |= 0x14;
+ } else {
+ if (*ctrlw == 5)
+ *ctrlw = 4;
+ code |= (*ctrlw << 2);
+ }
+ } else {
+ code -= 2;
+ if (*ctrlw >= 24) {
+ *ctrlw = 24;
+ code |= 0x1e;
+ } else if (*ctrlw >= 16) {
+ *ctrlw = 16;
+ code |= 0x1c;
+ } else if (*ctrlw >= 12) {
+ *ctrlw = 12;
+ code |= 0x1a;
+ } else {
+ *ctrlw = 8;
+ code |= 0x18;
+ }
+ }
+
+ *msgsl = (*msgsl * *ctrlw) - SLIM_FRM_SLOTS_PER_SUPERFRAME -
+ SLIM_GDE_SLOTS_PER_SUPERFRAME;
+ return code;
+}
+
+static void shiftsegoffsets(struct slim_controller *ctrl, struct slim_ich **ach,
+ int sz, u32 shft)
+{
+ int i;
+ u32 oldoff;
+ for (i = 0; i < sz; i++) {
+ struct slim_ich *slc;
+ if (ach[i] == NULL)
+ continue;
+ slc = ach[i];
+ if (slc->state == SLIM_CH_PENDING_REMOVAL)
+ continue;
+ oldoff = slc->newoff;
+ slc->newoff += shft;
+ /* seg. offset must be <= interval */
+ if (slc->newoff >= slc->newintr)
+ slc->newoff -= slc->newintr;
+ }
+}
+
+/*
+ * Find last chan in corresponding list, we will use to know when we
+* have done scheduling all channels in that list
+*/
+static int slim_get_last(struct slim_ich **chc, int num_ch)
+{
+ int last = num_ch - 1;
+ while (last >= 0) {
+ if (chc[last] != NULL &&
+ (chc[last])->state !=
+ SLIM_CH_PENDING_REMOVAL)
+ break;
+ last = last - 1;
+ }
+ return last;
+}
+
+/*
+ * Find first channels with coeff 1 & 3 as starting points for
+ * scheduling
+ */
+static int slim_get_coeff(struct slim_ich **chc, int num_ch)
+{
+ int coeff;
+ for (coeff = 0; coeff < num_ch; coeff++) {
+ struct slim_ich *slc = chc[coeff];
+ if (slc->state == SLIM_CH_PENDING_REMOVAL)
+ continue;
+ else
+ break;
+ }
+ return coeff;
+}
+static int slim_sched_coeff1_chans(struct slim_controller *ctrl, int coeff1,
+ int clkgear, int *subfrml, int *ctrlw)
+{
+ struct slim_ich *slc1 = ctrl->sched.chc1[coeff1];
+ u32 expshft = SLIM_MAX_CLK_GEAR - clkgear;
+ int curexp, finalexp;
+ u32 curintr, curmaxsl;
+ int opensl1[2];
+ int maxctrlw1;
+ int last1 = slim_get_last(ctrl->sched.chc1, ctrl->sched.num_cc1);
+
+ finalexp = (ctrl->sched.chc1[last1])->rootexp;
+ curexp = (int)expshft - 1;
+
+ curintr = (SLIM_MAX_INTR_COEFF_1 * 2) >> (curexp + 1);
+ curmaxsl = curintr >> 1;
+ opensl1[0] = opensl1[1] = curmaxsl;
+
+ while ((coeff1 < ctrl->sched.num_cc1) || (curintr > 24)) {
+ curintr >>= 1;
+ curmaxsl >>= 1;
+
+ /* update 4K family open slot records */
+ if (opensl1[1] < opensl1[0])
+ opensl1[1] -= curmaxsl;
+ else
+ opensl1[1] = opensl1[0] - curmaxsl;
+ opensl1[0] = curmaxsl;
+ if (opensl1[1] < 0) {
+ opensl1[0] += opensl1[1];
+ opensl1[1] = 0;
+ }
+ if (opensl1[0] <= 0) {
+ dev_err(&ctrl->dev, "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ curexp++;
+ /* schedule 4k family channels */
+ while ((coeff1 < ctrl->sched.num_cc1) && (curexp ==
+ (int)(slc1->rootexp + expshft))) {
+ if (slc1->state == SLIM_CH_PENDING_REMOVAL) {
+ coeff1++;
+ slc1 = ctrl->sched.chc1[coeff1];
+ continue;
+ }
+ if (opensl1[1] >= opensl1[0] ||
+ (finalexp == (int)slc1->rootexp &&
+ curintr <= 24 &&
+ opensl1[0] == curmaxsl)) {
+ opensl1[1] -= slc1->seglen;
+ slc1->newoff = curmaxsl + opensl1[1];
+ if (opensl1[1] < 0 &&
+ opensl1[0] == curmaxsl) {
+ opensl1[0] += opensl1[1];
+ opensl1[1] = 0;
+ if (opensl1[0] < 0) {
+ dev_dbg(&ctrl->dev,
+ "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ }
+ } else {
+ if (slc1->seglen > opensl1[0]) {
+ dev_dbg(&ctrl->dev,
+ "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ slc1->newoff = opensl1[0] -
+ slc1->seglen;
+ opensl1[0] = slc1->newoff;
+ }
+ slc1->newintr = curintr;
+ coeff1++;
+ slc1 = ctrl->sched.chc1[coeff1];
+ }
+ }
+ /* Leave some slots for messaging space */
+ if (opensl1[1] == 0 && opensl1[0] == 0)
+ return -EXFULL;
+ if (opensl1[1] > opensl1[0]) {
+ int temp = opensl1[0];
+ opensl1[0] = opensl1[1];
+ opensl1[1] = temp;
+ shiftsegoffsets(ctrl, ctrl->sched.chc1,
+ ctrl->sched.num_cc1, curmaxsl);
+ }
+ /* choose subframe mode to maximize bw */
+ maxctrlw1 = opensl1[0];
+ if (opensl1[0] == curmaxsl)
+ maxctrlw1 += opensl1[1];
+ if (curintr >= 24) {
+ *subfrml = 24;
+ *ctrlw = maxctrlw1;
+ } else if (curintr == 12) {
+ if (maxctrlw1 > opensl1[1] * 4) {
+ *subfrml = 24;
+ *ctrlw = maxctrlw1;
+ } else {
+ *subfrml = 6;
+ *ctrlw = opensl1[1];
+ }
+ } else {
+ *subfrml = 6;
+ *ctrlw = maxctrlw1;
+ }
+ return 0;
+}
+
+static int slim_sched_coeff1_3_chans(struct slim_controller *ctrl, int coeff1,
+ int coeff3, int clkgear, int *subfrml, int *ctrlw)
+{
+ struct slim_ich *slc1 = NULL;
+ struct slim_ich *slc3 = ctrl->sched.chc3[coeff3];
+ u32 expshft = SLIM_MAX_CLK_GEAR - clkgear;
+ int curexp, finalexp, exp1;
+ u32 curintr, curmaxsl;
+ int opensl3[2];
+ int opensl1[6];
+ bool opensl1valid = false;
+ int maxctrlw1, maxctrlw3, i;
+ int last1 = slim_get_last(ctrl->sched.chc1, ctrl->sched.num_cc1);
+ int last3 = slim_get_last(ctrl->sched.chc3, ctrl->sched.num_cc3);
+ finalexp = (ctrl->sched.chc3[last3])->rootexp;
+ if (last1 >= 0) {
+ slc1 = ctrl->sched.chc1[coeff1];
+ exp1 = (ctrl->sched.chc1[last1])->rootexp;
+ if (exp1 > finalexp)
+ finalexp = exp1;
+ }
+ curexp = (int)expshft - 1;
+
+ curintr = (SLIM_MAX_INTR_COEFF_3 * 2) >> (curexp + 1);
+ curmaxsl = curintr >> 1;
+ opensl3[0] = opensl3[1] = curmaxsl;
+
+ while (coeff1 < ctrl->sched.num_cc1 ||
+ coeff3 < ctrl->sched.num_cc3 ||
+ curintr > 32) {
+ curintr >>= 1;
+ curmaxsl >>= 1;
+
+ /* update 12k family open slot records */
+ if (opensl3[1] < opensl3[0])
+ opensl3[1] -= curmaxsl;
+ else
+ opensl3[1] = opensl3[0] - curmaxsl;
+ opensl3[0] = curmaxsl;
+ if (opensl3[1] < 0) {
+ opensl3[0] += opensl3[1];
+ opensl3[1] = 0;
+ }
+ if (opensl3[0] <= 0) {
+ dev_err(&ctrl->dev, "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ curexp++;
+
+ /* schedule 12k family channels */
+ while (coeff3 < ctrl->sched.num_cc3 &&
+ curexp == (int)slc3->rootexp + expshft) {
+ if (slc3->state == SLIM_CH_PENDING_REMOVAL) {
+ coeff3++;
+ slc3 = ctrl->sched.chc3[coeff3];
+ continue;
+ }
+ opensl1valid = false;
+ if (opensl3[1] >= opensl3[0] ||
+ (finalexp == (int)slc3->rootexp &&
+ curintr <= 32 &&
+ opensl3[0] == curmaxsl &&
+ last1 < 0)) {
+ opensl3[1] -= slc3->seglen;
+ slc3->newoff = curmaxsl + opensl3[1];
+ if (opensl3[1] < 0 &&
+ opensl3[0] == curmaxsl) {
+ opensl3[0] += opensl3[1];
+ opensl3[1] = 0;
+ }
+ if (opensl3[0] < 0) {
+ dev_err(&ctrl->dev,
+ "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ } else {
+ if (slc3->seglen > opensl3[0]) {
+ dev_err(&ctrl->dev,
+ "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ slc3->newoff = opensl3[0] -
+ slc3->seglen;
+ opensl3[0] = slc3->newoff;
+ }
+ slc3->newintr = curintr;
+ coeff3++;
+ slc3 = ctrl->sched.chc3[coeff3];
+ }
+ /* update 4k openslot records */
+ if (opensl1valid == false) {
+ for (i = 0; i < 3; i++) {
+ opensl1[i * 2] = opensl3[0];
+ opensl1[(i * 2) + 1] = opensl3[1];
+ }
+ } else {
+ int opensl1p[6];
+ memcpy(opensl1p, opensl1, sizeof(opensl1));
+ for (i = 0; i < 3; i++) {
+ if (opensl1p[i] < opensl1p[i + 3])
+ opensl1[(i * 2) + 1] =
+ opensl1p[i];
+ else
+ opensl1[(i * 2) + 1] =
+ opensl1p[i + 3];
+ }
+ for (i = 0; i < 3; i++) {
+ opensl1[(i * 2) + 1] -= curmaxsl;
+ opensl1[i * 2] = curmaxsl;
+ if (opensl1[(i * 2) + 1] < 0) {
+ opensl1[i * 2] +=
+ opensl1[(i * 2) + 1];
+ opensl1[(i * 2) + 1] = 0;
+ }
+ if (opensl1[i * 2] < 0) {
+ dev_err(&ctrl->dev,
+ "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ }
+ }
+ /* schedule 4k family channels */
+ while (coeff1 < ctrl->sched.num_cc1 &&
+ curexp == (int)slc1->rootexp + expshft) {
+ /* searchorder effective when opensl valid */
+ static const int srcho[] = { 5, 2, 4, 1, 3, 0 };
+ int maxopensl = 0;
+ int maxi = 0;
+ if (slc1->state == SLIM_CH_PENDING_REMOVAL) {
+ coeff1++;
+ slc1 = ctrl->sched.chc1[coeff1];
+ continue;
+ }
+ opensl1valid = true;
+ for (i = 0; i < 6; i++) {
+ if (opensl1[srcho[i]] > maxopensl) {
+ maxopensl = opensl1[srcho[i]];
+ maxi = srcho[i];
+ }
+ }
+ opensl1[maxi] -= slc1->seglen;
+ slc1->newoff = (curmaxsl * maxi) +
+ opensl1[maxi];
+ if (opensl1[maxi] < 0) {
+ if (((maxi & 1) == 1) &&
+ (opensl1[maxi - 1] == curmaxsl)) {
+ opensl1[maxi - 1] +=
+ opensl1[maxi];
+ if (opensl3[0] >
+ opensl1[maxi - 1])
+ opensl3[0] =
+ opensl1[maxi - 1];
+ opensl3[1] = 0;
+ opensl1[maxi] = 0;
+ if (opensl1[maxi - 1] < 0) {
+ dev_err(&ctrl->dev,
+ "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ } else {
+ dev_err(&ctrl->dev,
+ "reconfig failed:%d\n",
+ __LINE__);
+ return -EXFULL;
+ }
+ } else {
+ if (opensl3[maxi & 1] > opensl1[maxi])
+ opensl3[maxi & 1] =
+ opensl1[maxi];
+ }
+ slc1->newintr = curintr * 3;
+ coeff1++;
+ slc1 = ctrl->sched.chc1[coeff1];
+ }
+ }
+ /* Leave some slots for messaging space */
+ if (opensl3[1] == 0 && opensl3[0] == 0)
+ return -EXFULL;
+ /* swap 1st and 2nd bucket if 2nd bucket has more open slots */
+ if (opensl3[1] > opensl3[0]) {
+ int temp = opensl3[0];
+ opensl3[0] = opensl3[1];
+ opensl3[1] = temp;
+ temp = opensl1[5];
+ opensl1[5] = opensl1[4];
+ opensl1[4] = opensl1[3];
+ opensl1[3] = opensl1[2];
+ opensl1[2] = opensl1[1];
+ opensl1[1] = opensl1[0];
+ opensl1[0] = temp;
+ shiftsegoffsets(ctrl, ctrl->sched.chc1,
+ ctrl->sched.num_cc1, curmaxsl);
+ shiftsegoffsets(ctrl, ctrl->sched.chc3,
+ ctrl->sched.num_cc3, curmaxsl);
+ }
+ /* subframe mode to maximize BW */
+ maxctrlw3 = opensl3[0];
+ maxctrlw1 = opensl1[0];
+ if (opensl3[0] == curmaxsl)
+ maxctrlw3 += opensl3[1];
+ for (i = 0; i < 5 && opensl1[i] == curmaxsl; i++)
+ maxctrlw1 += opensl1[i + 1];
+ if (curintr >= 32) {
+ *subfrml = 32;
+ *ctrlw = maxctrlw3;
+ } else if (curintr == 16) {
+ if (maxctrlw3 > (opensl3[1] * 4)) {
+ *subfrml = 32;
+ *ctrlw = maxctrlw3;
+ } else {
+ *subfrml = 8;
+ *ctrlw = opensl3[1];
+ }
+ } else {
+ if ((maxctrlw1 * 8) >= (maxctrlw3 * 24)) {
+ *subfrml = 24;
+ *ctrlw = maxctrlw1;
+ } else {
+ *subfrml = 8;
+ *ctrlw = maxctrlw3;
+ }
+ }
+ return 0;
+}
+
+static int slim_sched_chans(struct slim_device *sb, u32 clkgear,
+ u32 *ctrlw, u32 *subfrml)
+{
+ int ret;
+ struct slim_controller *ctrl = sb->ctrl;
+ int coeff1 = slim_get_coeff(ctrl->sched.chc1, ctrl->sched.num_cc1);
+ int coeff3 = slim_get_coeff(ctrl->sched.chc3, ctrl->sched.num_cc3);
+
+ if (coeff3 == ctrl->sched.num_cc3 && coeff1 == ctrl->sched.num_cc1) {
+ *ctrlw = 8;
+ *subfrml = 8;
+ return 0;
+ } else if (coeff3 == ctrl->sched.num_cc3)
+ ret = slim_sched_coeff1_chans(ctrl, coeff1, clkgear,
+ ctrlw, subfrml);
+ else
+ ret = slim_sched_coeff1_3_chans(ctrl, coeff1, coeff3,
+ clkgear, ctrlw, subfrml);
+ return ret;
+}
+
+#ifdef DEBUG
+static int slim_verifychansched(struct slim_controller *ctrl, u32 ctrlw,
+ u32 subfrml, u32 clkgear)
+{
+ int sl, i;
+ int cc1 = 0;
+ int cc3 = 0;
+ struct slim_ich *slc = NULL;
+ if (!ctrl->sched.slots)
+ return 0;
+ memset(ctrl->sched.slots, 0, SLIM_SL_PER_SUPERFRAME);
+ dev_dbg(&ctrl->dev, "Clock gear is:%d\n", clkgear);
+ for (sl = 0; sl < SLIM_SL_PER_SUPERFRAME; sl += subfrml) {
+ for (i = 0; i < ctrlw; i++)
+ ctrl->sched.slots[sl + i] = 33;
+ }
+ while (cc1 < ctrl->sched.num_cc1) {
+ slc = ctrl->sched.chc1[cc1];
+ if (slc == NULL) {
+ dev_err(&ctrl->dev, "SLC1 null in verify: chan%d\n",
+ cc1);
+ return -EIO;
+ }
+ dev_dbg(&ctrl->dev, "chan:%d, offset:%d, intr:%d, seglen:%d\n",
+ (slc - ctrl->chans), slc->newoff,
+ slc->newintr, slc->seglen);
+
+ if (slc->state != SLIM_CH_PENDING_REMOVAL) {
+ for (sl = slc->newoff;
+ sl < SLIM_SL_PER_SUPERFRAME;
+ sl += slc->newintr) {
+ for (i = 0; i < slc->seglen; i++) {
+ if (ctrl->sched.slots[sl + i])
+ return -EXFULL;
+ ctrl->sched.slots[sl + i] = cc1 + 1;
+ }
+ }
+ }
+ cc1++;
+ }
+ while (cc3 < ctrl->sched.num_cc3) {
+ slc = ctrl->sched.chc3[cc3];
+ if (slc == NULL) {
+ dev_err(&ctrl->dev, "SLC3 null in verify: chan%d\n",
+ cc3);
+ return -EIO;
+ }
+ dev_dbg(&ctrl->dev, "chan:%d, offset:%d, intr:%d, seglen:%d\n",
+ (slc - ctrl->chans), slc->newoff,
+ slc->newintr, slc->seglen);
+ if (slc->state != SLIM_CH_PENDING_REMOVAL) {
+ for (sl = slc->newoff;
+ sl < SLIM_SL_PER_SUPERFRAME;
+ sl += slc->newintr) {
+ for (i = 0; i < slc->seglen; i++) {
+ if (ctrl->sched.slots[sl + i])
+ return -EXFULL;
+ ctrl->sched.slots[sl + i] = cc3 + 1;
+ }
+ }
+ }
+ cc3++;
+ }
+
+ return 0;
+}
+#else
+static int slim_verifychansched(struct slim_controller *ctrl, u32 ctrlw,
+ u32 subfrml, u32 clkgear)
+{
+ return 0;
+}
+#endif
+
+static void slim_sort_chan_grp(struct slim_controller *ctrl,
+ struct slim_ich *slc)
+{
+ u8 last = (u8)-1;
+ u8 second = 0;
+
+ for (; last > 0; last--) {
+ struct slim_ich *slc1 = slc;
+ struct slim_ich *slc2;
+ u8 next = SLIM_HDL_TO_CHIDX(slc1->nextgrp);
+ slc2 = &ctrl->chans[next];
+ for (second = 1; second <= last && slc2 &&
+ (slc2->state == SLIM_CH_ACTIVE ||
+ slc2->state == SLIM_CH_PENDING_ACTIVE); second++) {
+ if (slc1->newoff > slc2->newoff) {
+ u32 temp = slc2->newoff;
+ slc2->newoff = slc1->newoff;
+ slc1->newoff = temp;
+ }
+ if (slc2->nextgrp & SLIM_END_GRP) {
+ last = second;
+ break;
+ }
+ slc1 = slc2;
+ next = SLIM_HDL_TO_CHIDX(slc1->nextgrp);
+ slc2 = &ctrl->chans[next];
+ }
+ if (slc2 == NULL)
+ last = second - 1;
+ }
+}
+
+
+static int slim_allocbw(struct slim_device *sb, int *subfrmc, int *clkgear)
+{
+ u32 msgsl = 0;
+ u32 ctrlw = 0;
+ u32 subfrml = 0;
+ int ret = -EIO;
+ struct slim_controller *ctrl = sb->ctrl;
+ u32 usedsl = ctrl->sched.usedslots + ctrl->sched.pending_msgsl;
+ u32 availsl = SLIM_SL_PER_SUPERFRAME - SLIM_FRM_SLOTS_PER_SUPERFRAME -
+ SLIM_GDE_SLOTS_PER_SUPERFRAME;
+ *clkgear = SLIM_MAX_CLK_GEAR;
+
+ dev_dbg(&ctrl->dev, "used sl:%u, availlable sl:%u\n", usedsl, availsl);
+ dev_dbg(&ctrl->dev, "pending:chan sl:%u, :msg sl:%u, clkgear:%u\n",
+ ctrl->sched.usedslots,
+ ctrl->sched.pending_msgsl, *clkgear);
+ /*
+ * If number of slots are 0, that means channels are inactive.
+ * It is very likely that the manager will call clock pause very soon.
+ * By making sure that bus is in MAX_GEAR, clk pause sequence will take
+ * minimum amount of time.
+ */
+ if (ctrl->sched.usedslots != 0) {
+ while ((usedsl * 2 <= availsl) && (*clkgear > ctrl->min_cg)) {
+ *clkgear -= 1;
+ usedsl *= 2;
+ }
+ }
+
+ /*
+ * Try scheduling data channels at current clock gear, if all channels
+ * can be scheduled, or reserved BW can't be satisfied, increase clock
+ * gear and try again
+ */
+ for (; *clkgear <= ctrl->max_cg; (*clkgear)++) {
+ ret = slim_sched_chans(sb, *clkgear, &ctrlw, &subfrml);
+
+ if (ret == 0) {
+ *subfrmc = getsubfrmcoding(&ctrlw, &subfrml, &msgsl);
+ if ((msgsl >> (ctrl->max_cg - *clkgear) <
+ ctrl->sched.pending_msgsl) &&
+ (*clkgear < ctrl->max_cg))
+ continue;
+ else
+ break;
+ }
+ }
+ if (ret == 0) {
+ int i;
+ /* Sort channel-groups */
+ for (i = 0; i < ctrl->sched.num_cc1; i++) {
+ struct slim_ich *slc = ctrl->sched.chc1[i];
+ if (slc->state == SLIM_CH_PENDING_REMOVAL)
+ continue;
+ if ((slc->nextgrp & SLIM_START_GRP) &&
+ !(slc->nextgrp & SLIM_END_GRP)) {
+ slim_sort_chan_grp(ctrl, slc);
+ }
+ }
+ for (i = 0; i < ctrl->sched.num_cc3; i++) {
+ struct slim_ich *slc = ctrl->sched.chc3[i];
+ if (slc->state == SLIM_CH_PENDING_REMOVAL)
+ continue;
+ if ((slc->nextgrp & SLIM_START_GRP) &&
+ !(slc->nextgrp & SLIM_END_GRP)) {
+ slim_sort_chan_grp(ctrl, slc);
+ }
+ }
+
+ ret = slim_verifychansched(ctrl, ctrlw, subfrml, *clkgear);
+ }
+
+ return ret;
+}
+
+static void slim_change_existing_chans(struct slim_controller *ctrl, int coeff)
+{
+ struct slim_ich **arr;
+ int len, i;
+ if (coeff == SLIM_COEFF_1) {
+ arr = ctrl->sched.chc1;
+ len = ctrl->sched.num_cc1;
+ } else {
+ arr = ctrl->sched.chc3;
+ len = ctrl->sched.num_cc3;
+ }
+ for (i = 0; i < len; i++) {
+ struct slim_ich *slc = arr[i];
+ if (slc->state == SLIM_CH_ACTIVE ||
+ slc->state == SLIM_CH_SUSPENDED)
+ slc->offset = slc->newoff;
+ slc->interval = slc->newintr;
+ }
+}
+static void slim_chan_changes(struct slim_device *sb, bool revert)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ while (!list_empty(&sb->mark_define)) {
+ struct slim_ich *slc;
+ struct slim_pending_ch *pch =
+ list_entry(sb->mark_define.next,
+ struct slim_pending_ch, pending);
+ slc = &ctrl->chans[pch->chan];
+ if (revert) {
+ if (slc->state == SLIM_CH_PENDING_ACTIVE) {
+ u32 sl = slc->seglen << slc->rootexp;
+ if (slc->coeff == SLIM_COEFF_3)
+ sl *= 3;
+ ctrl->sched.usedslots -= sl;
+ slim_remove_ch(ctrl, slc);
+ slc->state = SLIM_CH_DEFINED;
+ }
+ } else {
+ slc->state = SLIM_CH_ACTIVE;
+ slc->def++;
+ }
+ list_del_init(&pch->pending);
+ kfree(pch);
+ }
+
+ while (!list_empty(&sb->mark_removal)) {
+ struct slim_pending_ch *pch =
+ list_entry(sb->mark_removal.next,
+ struct slim_pending_ch, pending);
+ struct slim_ich *slc = &ctrl->chans[pch->chan];
+ u32 sl = slc->seglen << slc->rootexp;
+ if (revert) {
+ if (slc->coeff == SLIM_COEFF_3)
+ sl *= 3;
+ ctrl->sched.usedslots += sl;
+ slc->def = 1;
+ slc->state = SLIM_CH_ACTIVE;
+ } else
+ slim_remove_ch(ctrl, slc);
+ list_del_init(&pch->pending);
+ kfree(pch);
+ }
+
+ while (!list_empty(&sb->mark_suspend)) {
+ struct slim_pending_ch *pch =
+ list_entry(sb->mark_suspend.next,
+ struct slim_pending_ch, pending);
+ struct slim_ich *slc = &ctrl->chans[pch->chan];
+ if (revert)
+ slc->state = SLIM_CH_ACTIVE;
+ list_del_init(&pch->pending);
+ kfree(pch);
+ }
+ /* Change already active channel if reconfig succeeded */
+ if (!revert) {
+ slim_change_existing_chans(ctrl, SLIM_COEFF_1);
+ slim_change_existing_chans(ctrl, SLIM_COEFF_3);
+ }
+}
+
+/*
+ * slim_reconfigure_now: Request reconfiguration now.
+ * @sb: client handle
+ * This API does what commit flag in other scheduling APIs do.
+ * -EXFULL is returned if there is no space in TDM to reserve the
+ * bandwidth. -EBUSY is returned if reconfiguration request is already in
+ * progress.
+ */
+int slim_reconfigure_now(struct slim_device *sb)
+{
+ u8 i;
+ u8 wbuf[4];
+ u32 clkgear, subframe;
+ u32 curexp;
+ int ret;
+ struct slim_controller *ctrl = sb->ctrl;
+ u32 expshft;
+ u32 segdist;
+ struct slim_pending_ch *pch;
+
+ mutex_lock(&ctrl->sched.m_reconf);
+ mutex_lock(&ctrl->m_ctrl);
+ ctrl->sched.pending_msgsl += sb->pending_msgsl - sb->cur_msgsl;
+ list_for_each_entry(pch, &sb->mark_define, pending) {
+ struct slim_ich *slc = &ctrl->chans[pch->chan];
+ slim_add_ch(ctrl, slc);
+ if (slc->state < SLIM_CH_ACTIVE)
+ slc->state = SLIM_CH_PENDING_ACTIVE;
+ }
+
+ list_for_each_entry(pch, &sb->mark_removal, pending) {
+ struct slim_ich *slc = &ctrl->chans[pch->chan];
+ u32 sl = slc->seglen << slc->rootexp;
+ if (slc->coeff == SLIM_COEFF_3)
+ sl *= 3;
+ ctrl->sched.usedslots -= sl;
+ slc->state = SLIM_CH_PENDING_REMOVAL;
+ }
+ list_for_each_entry(pch, &sb->mark_suspend, pending) {
+ struct slim_ich *slc = &ctrl->chans[pch->chan];
+ slc->state = SLIM_CH_SUSPENDED;
+ }
+ mutex_unlock(&ctrl->m_ctrl);
+
+ ret = slim_allocbw(sb, &subframe, &clkgear);
+
+ if (!ret) {
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_BEGIN_RECONFIGURATION, 0, SLIM_MSG_MT_CORE,
+ NULL, NULL, 0, 3, NULL, 0, NULL);
+ dev_dbg(&ctrl->dev, "sending begin_reconfig:ret:%d\n", ret);
+ }
+
+ if (!ret && subframe != ctrl->sched.subfrmcode) {
+ wbuf[0] = (u8)(subframe & 0xFF);
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_NEXT_SUBFRAME_MODE, 0, SLIM_MSG_MT_CORE,
+ NULL, (u8 *)&subframe, 1, 4, NULL, 0, NULL);
+ dev_dbg(&ctrl->dev, "sending subframe:%d,ret:%d\n",
+ (int)wbuf[0], ret);
+ }
+ if (!ret && clkgear != ctrl->clkgear) {
+ wbuf[0] = (u8)(clkgear & 0xFF);
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_NEXT_CLOCK_GEAR, 0, SLIM_MSG_MT_CORE,
+ NULL, wbuf, 1, 4, NULL, 0, NULL);
+ dev_dbg(&ctrl->dev, "sending clkgear:%d,ret:%d\n",
+ (int)wbuf[0], ret);
+ }
+ if (ret)
+ goto revert_reconfig;
+
+ expshft = SLIM_MAX_CLK_GEAR - clkgear;
+ /* activate/remove channel */
+ list_for_each_entry(pch, &sb->mark_define, pending) {
+ struct slim_ich *slc = &ctrl->chans[pch->chan];
+ /* Define content */
+ wbuf[0] = slc->chan;
+ wbuf[1] = slc->prrate;
+ wbuf[2] = slc->prop.dataf | (slc->prop.auxf << 4);
+ wbuf[3] = slc->prop.sampleszbits / SLIM_CL_PER_SL;
+ dev_dbg(&ctrl->dev, "define content, activate:%x, %x, %x, %x\n",
+ wbuf[0], wbuf[1], wbuf[2], wbuf[3]);
+ /* Right now, channel link bit is not supported */
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_NEXT_DEFINE_CONTENT, 0,
+ SLIM_MSG_MT_CORE, NULL, (u8 *)&wbuf, 4, 7,
+ NULL, 0, NULL);
+ if (ret)
+ goto revert_reconfig;
+
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL, 0,
+ SLIM_MSG_MT_CORE, NULL, (u8 *)&wbuf, 1, 4,
+ NULL, 0, NULL);
+ if (ret)
+ goto revert_reconfig;
+ }
+
+ list_for_each_entry(pch, &sb->mark_removal, pending) {
+ struct slim_ich *slc = &ctrl->chans[pch->chan];
+ dev_dbg(&ctrl->dev, "remove chan:%x\n", pch->chan);
+ wbuf[0] = slc->chan;
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_NEXT_REMOVE_CHANNEL, 0,
+ SLIM_MSG_MT_CORE, NULL, wbuf, 1, 4,
+ NULL, 0, NULL);
+ if (ret)
+ goto revert_reconfig;
+ }
+ list_for_each_entry(pch, &sb->mark_suspend, pending) {
+ struct slim_ich *slc = &ctrl->chans[pch->chan];
+ dev_dbg(&ctrl->dev, "suspend chan:%x\n", pch->chan);
+ wbuf[0] = slc->chan;
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL, 0,
+ SLIM_MSG_MT_CORE, NULL, wbuf, 1, 4,
+ NULL, 0, NULL);
+ if (ret)
+ goto revert_reconfig;
+ }
+
+ /* Define CC1 channel */
+ for (i = 0; i < ctrl->sched.num_cc1; i++) {
+ struct slim_ich *slc = ctrl->sched.chc1[i];
+ if (slc->state == SLIM_CH_PENDING_REMOVAL)
+ continue;
+ curexp = slc->rootexp + expshft;
+ segdist = (slc->newoff << curexp) & 0x1FF;
+ expshft = SLIM_MAX_CLK_GEAR - clkgear;
+ dev_dbg(&ctrl->dev, "new-intr:%d, old-intr:%d, dist:%d\n",
+ slc->newintr, slc->interval, segdist);
+ dev_dbg(&ctrl->dev, "new-off:%d, old-off:%d\n",
+ slc->newoff, slc->offset);
+
+ if (slc->state < SLIM_CH_ACTIVE || slc->def < slc->ref ||
+ slc->newintr != slc->interval ||
+ slc->newoff != slc->offset) {
+ segdist |= 0x200;
+ segdist >>= curexp;
+ segdist |= (slc->newoff << (curexp + 1)) & 0xC00;
+ wbuf[0] = slc->chan;
+ wbuf[1] = (u8)(segdist & 0xFF);
+ wbuf[2] = (u8)((segdist & 0xF00) >> 8) |
+ (slc->prop.prot << 4);
+ wbuf[3] = slc->seglen;
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_NEXT_DEFINE_CHANNEL, 0,
+ SLIM_MSG_MT_CORE, NULL, (u8 *)wbuf, 4,
+ 7, NULL, 0, NULL);
+ if (ret)
+ goto revert_reconfig;
+ }
+ }
+
+ /* Define CC3 channels */
+ for (i = 0; i < ctrl->sched.num_cc3; i++) {
+ struct slim_ich *slc = ctrl->sched.chc3[i];
+ if (slc->state == SLIM_CH_PENDING_REMOVAL)
+ continue;
+ curexp = slc->rootexp + expshft;
+ segdist = (slc->newoff << curexp) & 0x1FF;
+ expshft = SLIM_MAX_CLK_GEAR - clkgear;
+ dev_dbg(&ctrl->dev, "new-intr:%d, old-intr:%d, dist:%d\n",
+ slc->newintr, slc->interval, segdist);
+ dev_dbg(&ctrl->dev, "new-off:%d, old-off:%d\n",
+ slc->newoff, slc->offset);
+
+ if (slc->state < SLIM_CH_ACTIVE || slc->def < slc->ref ||
+ slc->newintr != slc->interval ||
+ slc->newoff != slc->offset) {
+ segdist |= 0x200;
+ segdist >>= curexp;
+ segdist |= 0xC00;
+ wbuf[0] = slc->chan;
+ wbuf[1] = (u8)(segdist & 0xFF);
+ wbuf[2] = (u8)((segdist & 0xF00) >> 8) |
+ (slc->prop.prot << 4);
+ wbuf[3] = (u8)(slc->seglen);
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_NEXT_DEFINE_CHANNEL, 0,
+ SLIM_MSG_MT_CORE, NULL, (u8 *)wbuf, 4,
+ 7, NULL, 0, NULL);
+ if (ret)
+ goto revert_reconfig;
+ }
+ }
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_MC_RECONFIGURE_NOW, 0, SLIM_MSG_MT_CORE, NULL,
+ NULL, 0, 3, NULL, 0, NULL);
+ dev_dbg(&ctrl->dev, "reconfig now:ret:%d\n", ret);
+ if (!ret) {
+ mutex_lock(&ctrl->m_ctrl);
+ ctrl->sched.subfrmcode = subframe;
+ ctrl->clkgear = clkgear;
+ ctrl->sched.msgsl = ctrl->sched.pending_msgsl;
+ sb->cur_msgsl = sb->pending_msgsl;
+ slim_chan_changes(sb, false);
+ mutex_unlock(&ctrl->m_ctrl);
+ mutex_unlock(&ctrl->sched.m_reconf);
+ return 0;
+ }
+
+revert_reconfig:
+ mutex_lock(&ctrl->m_ctrl);
+ /* Revert channel changes */
+ slim_chan_changes(sb, true);
+ mutex_unlock(&ctrl->m_ctrl);
+ mutex_unlock(&ctrl->sched.m_reconf);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_reconfigure_now);
+
+static int add_pending_ch(struct list_head *listh, u8 chan)
+{
+ struct slim_pending_ch *pch;
+ pch = kmalloc(sizeof(struct slim_pending_ch), GFP_KERNEL);
+ if (!pch)
+ return -ENOMEM;
+ pch->chan = chan;
+ list_add_tail(&pch->pending, listh);
+ return 0;
+}
+
+/*
+ * slim_control_ch: Channel control API.
+ * @sb: client handle
+ * @chanh: group or channel handle to be controlled
+ * @chctrl: Control command (activate/suspend/remove)
+ * @commit: flag to indicate whether the control should take effect right-away.
+ * This API activates, removes or suspends a channel (or group of channels)
+ * chanh indicates the channel or group handle (returned by the define_ch API).
+ * Reconfiguration may be time-consuming since it can change all other active
+ * channel allocations on the bus, change in clock gear used by the slimbus,
+ * and change in the control space width used for messaging.
+ * commit makes sure that multiple channels can be activated/deactivated before
+ * reconfiguration is started.
+ * -EXFULL is returned if there is no space in TDM to reserve the bandwidth.
+ * -EISCONN/-ENOTCONN is returned if the channel is already connected or not
+ * yet defined.
+ * -EINVAL is returned if individual control of a grouped-channel is attempted.
+ */
+int slim_control_ch(struct slim_device *sb, u16 chanh,
+ enum slim_ch_control chctrl, bool commit)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ int ret = 0;
+ /* Get rid of the group flag in MSB if any */
+ u8 chan = SLIM_HDL_TO_CHIDX(chanh);
+ struct slim_ich *slc = &ctrl->chans[chan];
+ if (!(slc->nextgrp & SLIM_START_GRP))
+ return -EINVAL;
+
+ mutex_lock(&sb->sldev_reconf);
+ mutex_lock(&ctrl->m_ctrl);
+ do {
+ slc = &ctrl->chans[chan];
+ dev_dbg(&ctrl->dev, "chan:%d,ctrl:%d,def:%d", chan, chctrl,
+ slc->def);
+ if (slc->state < SLIM_CH_DEFINED) {
+ ret = -ENOTCONN;
+ break;
+ }
+ if (chctrl == SLIM_CH_SUSPEND) {
+ ret = add_pending_ch(&sb->mark_suspend, chan);
+ if (ret)
+ break;
+ } else if (chctrl == SLIM_CH_ACTIVATE) {
+ if (slc->state > SLIM_CH_ACTIVE) {
+ ret = -EISCONN;
+ break;
+ }
+ ret = add_pending_ch(&sb->mark_define, chan);
+ if (ret)
+ break;
+ } else {
+ if (slc->state < SLIM_CH_ACTIVE) {
+ ret = -ENOTCONN;
+ break;
+ }
+ if (slc->def > 0)
+ slc->def--;
+ if (slc->def == 0)
+ ret = add_pending_ch(&sb->mark_removal, chan);
+ if (ret)
+ break;
+ }
+
+ if (!(slc->nextgrp & SLIM_END_GRP))
+ chan = SLIM_HDL_TO_CHIDX(slc->nextgrp);
+ } while (!(slc->nextgrp & SLIM_END_GRP));
+ mutex_unlock(&ctrl->m_ctrl);
+ if (!ret && commit == true)
+ ret = slim_reconfigure_now(sb);
+ mutex_unlock(&sb->sldev_reconf);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_control_ch);
+
+/*
+ * slim_reservemsg_bw: Request to reserve bandwidth for messages.
+ * @sb: client handle
+ * @bw_bps: message bandwidth in bits per second to be requested
+ * @commit: indicates whether the reconfiguration needs to be acted upon.
+ * This API call can be grouped with slim_control_ch API call with only one of
+ * the APIs specifying the commit flag to avoid reconfiguration being called too
+ * frequently. -EXFULL is returned if there is no space in TDM to reserve the
+ * bandwidth. -EBUSY is returned if reconfiguration is requested, but a request
+ * is already in progress.
+ */
+int slim_reservemsg_bw(struct slim_device *sb, u32 bw_bps, bool commit)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+ int ret = 0;
+ int sl;
+ mutex_lock(&sb->sldev_reconf);
+ if ((bw_bps >> 3) >= ctrl->a_framer->rootfreq)
+ sl = SLIM_SL_PER_SUPERFRAME;
+ else {
+ sl = (bw_bps * (SLIM_CL_PER_SUPERFRAME_DIV8/SLIM_CL_PER_SL/2) +
+ (ctrl->a_framer->rootfreq/2 - 1)) /
+ (ctrl->a_framer->rootfreq/2);
+ }
+ dev_dbg(&ctrl->dev, "request:bw:%d, slots:%d, current:%d\n", bw_bps, sl,
+ sb->cur_msgsl);
+ sb->pending_msgsl = sl;
+ if (commit == true)
+ ret = slim_reconfigure_now(sb);
+ mutex_unlock(&sb->sldev_reconf);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(slim_reservemsg_bw);
+
+/*
+ * slim_ctrl_clk_pause: Called by slimbus controller to request clock to be
+ * paused or woken up out of clock pause
+ * or woken up from clock pause
+ * @ctrl: controller requesting bus to be paused or woken up
+ * @wakeup: Wakeup this controller from clock pause.
+ * @restart: Restart time value per spec used for clock pause. This value
+ * isn't used when controller is to be woken up.
+ * This API executes clock pause reconfiguration sequence if wakeup is false.
+ * If wakeup is true, controller's wakeup is called
+ * Slimbus clock is idle and can be disabled by the controller later.
+ */
+int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, u8 restart)
+{
+ int ret = 0;
+ int i;
+
+ if (wakeup == false && restart > SLIM_CLK_UNSPECIFIED)
+ return -EINVAL;
+ mutex_lock(&ctrl->m_ctrl);
+ if (wakeup) {
+ if (ctrl->clk_state == SLIM_CLK_ACTIVE) {
+ mutex_unlock(&ctrl->m_ctrl);
+ return 0;
+ }
+ wait_for_completion(&ctrl->pause_comp);
+ /*
+ * Slimbus framework will call controller wakeup
+ * Controller should make sure that it sets active framer
+ * out of clock pause by doing appropriate setting
+ */
+ if (ctrl->clk_state == SLIM_CLK_PAUSED && ctrl->wakeup)
+ ret = ctrl->wakeup(ctrl);
+ if (!ret)
+ ctrl->clk_state = SLIM_CLK_ACTIVE;
+ mutex_unlock(&ctrl->m_ctrl);
+ return ret;
+ } else {
+ switch (ctrl->clk_state) {
+ case SLIM_CLK_ENTERING_PAUSE:
+ case SLIM_CLK_PAUSE_FAILED:
+ /*
+ * If controller is already trying to enter clock pause,
+ * let it finish.
+ * In case of error, retry
+ * In both cases, previous clock pause has signalled
+ * completion.
+ */
+ wait_for_completion(&ctrl->pause_comp);
+ /* retry upon failure */
+ if (ctrl->clk_state == SLIM_CLK_PAUSE_FAILED) {
+ ctrl->clk_state = SLIM_CLK_ACTIVE;
+ break;
+ } else {
+ mutex_unlock(&ctrl->m_ctrl);
+ /*
+ * Signal completion so that wakeup can wait on
+ * it.
+ */
+ complete(&ctrl->pause_comp);
+ return 0;
+ }
+ break;
+ case SLIM_CLK_PAUSED:
+ /* already paused */
+ mutex_unlock(&ctrl->m_ctrl);
+ return 0;
+ case SLIM_CLK_ACTIVE:
+ default:
+ break;
+ }
+ }
+ /* Pending response for a message */
+ for (i = 0; i < ctrl->last_tid; i++) {
+ if (ctrl->txnt[i]) {
+ ret = -EBUSY;
+ mutex_unlock(&ctrl->m_ctrl);
+ return -EBUSY;
+ }
+ }
+ ctrl->clk_state = SLIM_CLK_ENTERING_PAUSE;
+ mutex_unlock(&ctrl->m_ctrl);
+
+ mutex_lock(&ctrl->sched.m_reconf);
+ /* Data channels active */
+ if (ctrl->sched.usedslots) {
+ ret = -EBUSY;
+ goto clk_pause_ret;
+ }
+
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_BEGIN_RECONFIGURATION,
+ 0, SLIM_MSG_MT_CORE, NULL, NULL, 0, 3, NULL, 0, NULL);
+ if (ret)
+ goto clk_pause_ret;
+
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_NEXT_PAUSE_CLOCK, 0,
+ SLIM_MSG_MT_CORE, NULL, &restart, 1, 4, NULL, 0, NULL);
+ if (ret)
+ goto clk_pause_ret;
+
+ ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
+ SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_RECONFIGURE_NOW, 0,
+ SLIM_MSG_MT_CORE, NULL, NULL, 0, 3, NULL, 0, NULL);
+ if (ret)
+ goto clk_pause_ret;
+
+clk_pause_ret:
+ if (ret)
+ ctrl->clk_state = SLIM_CLK_PAUSE_FAILED;
+ else
+ ctrl->clk_state = SLIM_CLK_PAUSED;
+ complete(&ctrl->pause_comp);
+ mutex_unlock(&ctrl->sched.m_reconf);
+ return ret;
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Slimbus module");
+MODULE_ALIAS("platform:slimbus");
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 501da4c..b631dbb 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -445,6 +445,13 @@ struct spi_device_id {
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
+struct slim_device_id {
+ __u16 manf_id, prod_code;
+ __u8 dev_index, instance;
+ kernel_ulong_t driver_data /* Data private to the driver */
+ __attribute__((aligned(sizeof(kernel_ulong_t))));
+};
+
/* dmi */
enum dmi_field {
DMI_NONE,
diff --git a/include/linux/of_slimbus.h b/include/linux/of_slimbus.h
new file mode 100644
index 0000000..8e1dc65
--- /dev/null
+++ b/include/linux/of_slimbus.h
@@ -0,0 +1,34 @@
+/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/slimbus/slimbus.h>
+#include <linux/of_irq.h>
+
+#ifdef CONFIG_OF_SLIMBUS
+/*
+ * of_slim_register_devices() - Register devices in the SLIMbus Device Tree
+ * @ctrl: slim_controller which devices should be registered to.
+ *
+ * This routine scans the SLIMbus Device Tree, allocating resources and
+ * creating slim_devices according to the SLIMbus Device Tree
+ * hierarchy. Details of this hierarchy can be found in
+ * Documentation/devicetree/bindings/slimbus. This routine is normally
+ * called from the probe routine of the driver registering as a
+ * slim_controller.
+ */
+extern int of_register_slim_devices(struct slim_controller *ctrl);
+#else
+static int of_register_slim_devices(struct slim_controller *ctrl)
+{
+ return 0;
+}
+#endif /* CONFIG_OF_SLIMBUS */
diff --git a/include/linux/slimbus/slimbus.h b/include/linux/slimbus/slimbus.h
new file mode 100644
index 0000000..ef3f614
--- /dev/null
+++ b/include/linux/slimbus/slimbus.h
@@ -0,0 +1,1072 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _LINUX_SLIMBUS_H
+#define _LINUX_SLIMBUS_H
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/mod_devicetable.h>
+
+#define SLIMBUS_NAME_SIZE 32
+/* Interfaces between SLIMbus manager drivers and SLIMbus infrastructure. */
+
+extern struct bus_type slimbus_type;
+
+/* Standard values per SLIMbus spec needed by controllers and devices */
+#define SLIM_CL_PER_SUPERFRAME 6144
+#define SLIM_CL_PER_SUPERFRAME_DIV8 (SLIM_CL_PER_SUPERFRAME >> 3)
+#define SLIM_MAX_CLK_GEAR 10
+#define SLIM_MIN_CLK_GEAR 1
+#define SLIM_CL_PER_SL 4
+#define SLIM_SL_PER_SUPERFRAME (SLIM_CL_PER_SUPERFRAME >> 2)
+#define SLIM_FRM_SLOTS_PER_SUPERFRAME 16
+#define SLIM_GDE_SLOTS_PER_SUPERFRAME 2
+
+/*
+ * SLIMbus message types. Related to interpretation of message code.
+ * Values are defined in Table 32 (slimbus spec 1.01.01)
+ */
+#define SLIM_MSG_MT_CORE 0x0
+#define SLIM_MSG_MT_DEST_REFERRED_CLASS 0x1
+#define SLIM_MSG_MT_DEST_REFERRED_USER 0x2
+#define SLIM_MSG_MT_SRC_REFERRED_CLASS 0x5
+#define SLIM_MSG_MT_SRC_REFERRED_USER 0x6
+
+/*
+ * SLIMbus core type Message Codes.
+ * Values are defined in Table 65 (slimbus spec 1.01.01)
+ */
+/* Device management messages */
+#define SLIM_MSG_MC_REPORT_PRESENT 0x1
+#define SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS 0x2
+#define SLIM_MSG_MC_RESET_DEVICE 0x4
+#define SLIM_MSG_MC_CHANGE_LOGICAL_ADDRESS 0x8
+#define SLIM_MSG_MC_CHANGE_ARBITRATION_PRIORITY 0x9
+#define SLIM_MSG_MC_REQUEST_SELF_ANNOUNCEMENT 0xC
+#define SLIM_MSG_MC_REPORT_ABSENT 0xF
+
+/* Data channel management messages */
+#define SLIM_MSG_MC_CONNECT_SOURCE 0x10
+#define SLIM_MSG_MC_CONNECT_SINK 0x11
+#define SLIM_MSG_MC_DISCONNECT_PORT 0x14
+#define SLIM_MSG_MC_CHANGE_CONTENT 0x18
+
+/* Information management messages */
+#define SLIM_MSG_MC_REQUEST_INFORMATION 0x20
+#define SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION 0x21
+#define SLIM_MSG_MC_REPLY_INFORMATION 0x24
+#define SLIM_MSG_MC_CLEAR_INFORMATION 0x28
+#define SLIM_MSG_MC_REPORT_INFORMATION 0x29
+
+/* Reconfiguration messages */
+#define SLIM_MSG_MC_BEGIN_RECONFIGURATION 0x40
+#define SLIM_MSG_MC_NEXT_ACTIVE_FRAMER 0x44
+#define SLIM_MSG_MC_NEXT_SUBFRAME_MODE 0x45
+#define SLIM_MSG_MC_NEXT_CLOCK_GEAR 0x46
+#define SLIM_MSG_MC_NEXT_ROOT_FREQUENCY 0x47
+#define SLIM_MSG_MC_NEXT_PAUSE_CLOCK 0x4A
+#define SLIM_MSG_MC_NEXT_RESET_BUS 0x4B
+#define SLIM_MSG_MC_NEXT_SHUTDOWN_BUS 0x4C
+#define SLIM_MSG_MC_NEXT_DEFINE_CHANNEL 0x50
+#define SLIM_MSG_MC_NEXT_DEFINE_CONTENT 0x51
+#define SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL 0x54
+#define SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL 0x55
+#define SLIM_MSG_MC_NEXT_REMOVE_CHANNEL 0x58
+#define SLIM_MSG_MC_RECONFIGURE_NOW 0x5F
+
+/*
+ * Clock pause flag to indicate that the reconfig message
+ * corresponds to clock pause sequence
+ */
+#define SLIM_MSG_CLK_PAUSE_SEQ_FLG (1U << 8)
+
+/* Value management messages */
+#define SLIM_MSG_MC_REQUEST_VALUE 0x60
+#define SLIM_MSG_MC_REQUEST_CHANGE_VALUE 0x61
+#define SLIM_MSG_MC_REPLY_VALUE 0x64
+#define SLIM_MSG_MC_CHANGE_VALUE 0x68
+
+/* Clock pause values defined in Table 66 (slimbus spec 1.01.01) */
+#define SLIM_CLK_FAST 0
+#define SLIM_CLK_CONST_PHASE 1
+#define SLIM_CLK_UNSPECIFIED 2
+
+struct slim_controller;
+struct slim_device;
+
+/*
+ * struct slim_eaddr: Elemental address for a slimbus device
+ * @manf_id: Manufacturer Id for the device
+ * @prod_code: Product code
+ * @dev_index: Device index
+ * @instance: Instance value
+ */
+struct slim_eaddr {
+ u16 manf_id;
+ u16 prod_code;
+ u8 dev_index;
+ u8 instance;
+};
+
+/* Destination type Values defined in Table 33 (slimbus spec 1.01.01) */
+#define SLIM_MSG_DEST_LOGICALADDR 0
+#define SLIM_MSG_DEST_ENUMADDR 1
+#define SLIM_MSG_DEST_BROADCAST 3
+
+/*
+ * @start_offset: Specifies starting offset in information/value element map
+ * @num_bytes: Can be 1, 2, 3, 4, 6, 8, 12, 16 per spec. This ensures that the
+ * message will fit in the 40-byte message limit and the slicesize can be
+ * compatible with values in table 21 (slimbus spec 1.01.01)
+ * @comp: Completion to indicate end of message-transfer. Used if client wishes
+ * to use the API asynchronously.
+ */
+struct slim_ele_access {
+ u16 start_offset;
+ u8 num_bytes;
+ struct completion *comp;
+};
+
+/*
+ * struct slim_framer - Represents Slimbus framer.
+ * Every controller may have multiple framers.
+ * Manager is responsible for framer hand-over.
+ * @e_addr: Elemental address of the framer.
+ * @rootfreq: Root Frequency at which the framer can run. This is maximum
+ * frequency (clock gear 10 per slimbus spec) at which the bus can operate.
+ * @superfreq: Superframes per root frequency. Every frame is 6144 cells (bits)
+ * per slimbus specification.
+ */
+struct slim_framer {
+ struct slim_eaddr e_addr;
+ int rootfreq;
+ int superfreq;
+};
+#define to_slim_framer(d) container_of(d, struct slim_framer, dev);
+
+/*
+ * struct slim_addrt: slimbus address used internally by the slimbus framework.
+ * @valid: If the device is still there or if the address can be reused.
+ * @listed: Each device is added to board-list either when it's enumerated, or
+ * when it's listed as part of boardinfo (whichever happens first).
+ * This field is set to true if the device is listed first through
+ * enumeration so that register_board_info doesn't add this again if it's
+ * called after the device was enumerated.
+ * @eaddr: Elemental address
+ * @laddr: It is possible that controller will set a predefined logical address
+ * rather than the one assigned by framework. (i.e. logical address may
+ * not be same as index into this table). This entry will store the
+ * logical address value for this enumeration address.
+ */
+struct slim_addrt {
+ bool valid;
+ bool listed;
+ struct slim_eaddr eaddr;
+ u8 laddr;
+};
+
+/*
+ * struct slim_msg_txn: Message to be sent by the controller.
+ * Linux framework uses this structure with drivers implementing controller.
+ * This structure has packet header, payload and buffer to be filled (if any)
+ * For the header information, refer to Table 34-36.
+ * @rl: Header field. remaining length.
+ * @mt: Header field. Message type.
+ * @mc: Header field. LSB is message code for type mt. Framework will set MSB to
+ * SLIM_MSG_CLK_PAUSE_SEQ_FLG in case "mc" in the reconfiguration sequence
+ * is for pausing the clock.
+ * @dt: Header field. Destination type.
+ * @ec: Element size. Used for elemental access APIs.
+ * @len: Length of payload. (excludes ec)
+ * @tid: Transaction ID. Used for messages expecting response.
+ * (e.g. relevant for mc = SLIM_MSG_MC_REQUEST_INFORMATION)
+ * @la: Logical address of the device this message is going to.
+ * (Not used when destination type is broadcast.)
+ * @rbuf: Buffer to be populated by controller when response is received.
+ * @wbuf: Payload of the message. (e.g. channel number for DATA channel APIs)
+ * @comp: Completion structure. Used by controller to notify response.
+ * (Field is relevant when tid is used)
+ */
+struct slim_msg_txn {
+ u8 rl;
+ u8 mt;
+ u16 mc;
+ u8 dt;
+ u16 ec;
+ u8 len;
+ u8 tid;
+ u8 la;
+ u8 *rbuf;
+ const u8 *wbuf;
+ struct completion *comp;
+};
+
+/* Internal port state used by slimbus framework to manage data-ports */
+enum slim_port_state {
+ SLIM_P_FREE,
+ SLIM_P_UNCFG,
+ SLIM_P_CFG,
+};
+
+/*
+ * enum slim_port_req: Request port type by user through APIs to manage ports
+ * User can request default, half-duplex or port to be used in multi-channel
+ * configuration. Default indicates a simplex port.
+ */
+enum slim_port_req {
+ SLIM_REQ_DEFAULT,
+ SLIM_REQ_HALF_DUP,
+ SLIM_REQ_MULTI_CH,
+};
+
+/*
+ * enum slim_port_cfg: Port configuration parameters requested.
+ * User can request no configuration, packed data, or MSB aligned data port
+ */
+enum slim_port_cfg {
+ SLIM_CFG_NONE,
+ SLIM_CFG_PACKED,
+ SLIM_CFG_ALIGN_MSB,
+};
+
+/* enum slim_port_flow: Port flow type (inbound/outbound). */
+enum slim_port_flow {
+ SLIM_SRC,
+ SLIM_SINK,
+};
+
+/* enum slim_port_err: Port errors */
+enum slim_port_err {
+ SLIM_P_INPROGRESS,
+ SLIM_P_OVERFLOW,
+ SLIM_P_UNDERFLOW,
+ SLIM_P_DISCONNECT,
+ SLIM_P_NOT_OWNED,
+};
+
+/*
+ * struct slim_port: Internal structure used by framework to manage ports
+ * @err: Port error if any for this port. Refer to enum above.
+ * @state: Port state. Refer to enum above.
+ * @req: Port request for this port.
+ * @cfg: Port configuration for this port.
+ * @flow: Flow type of this port.
+ * @ch: Channel association of this port.
+ * @xcomp: Completion to indicate error, data transfer done event.
+ * @ctrl: Controller to which this port belongs to. This is useful to associate
+ * port with the SW since port hardware interrupts may only contain port
+ * information.
+ */
+struct slim_port {
+ enum slim_port_err err;
+ enum slim_port_state state;
+ enum slim_port_req req;
+ enum slim_port_cfg cfg;
+ enum slim_port_flow flow;
+ struct slim_ch *ch;
+ struct completion *xcomp;
+ struct slim_controller *ctrl;
+};
+
+/*
+ * enum slim_ch_state: Channel state of a channel.
+ * Channel transition happens from free-to-allocated-to-defined-to-pending-
+ * active-to-active.
+ * Once active, channel can be removed or suspended. Suspended channels are
+ * still scheduled, but data transfer doesn't happen.
+ * Removed channels are not deallocated until dealloc_ch API is used.
+ * Deallocation reset channel state back to free.
+ * Removed channels can be defined with different parameters.
+ */
+enum slim_ch_state {
+ SLIM_CH_FREE,
+ SLIM_CH_ALLOCATED,
+ SLIM_CH_DEFINED,
+ SLIM_CH_PENDING_ACTIVE,
+ SLIM_CH_ACTIVE,
+ SLIM_CH_SUSPENDED,
+ SLIM_CH_PENDING_REMOVAL,
+};
+
+/*
+ * enum slim_ch_proto: Channel protocol used by the channel.
+ * Hard Isochronous channel is not scheduled if current frequency doesn't allow
+ * the channel to be run without flow-control.
+ * Auto isochronous channel will be scheduled as hard-isochronous or push-pull
+ * depending on current bus frequency.
+ * Currently, Push-pull or async or extended channels are not supported.
+ * For more details, refer to slimbus spec
+ */
+enum slim_ch_proto {
+ SLIM_HARD_ISO,
+ SLIM_AUTO_ISO,
+ SLIM_PUSH,
+ SLIM_PULL,
+ SLIM_ASYNC_SMPLX,
+ SLIM_ASYNC_HALF_DUP,
+ SLIM_EXT_SMPLX,
+ SLIM_EXT_HALF_DUP,
+};
+
+/*
+ * enum slim_ch_rate: Most commonly used frequency rate families.
+ * Use 1HZ for push-pull transport.
+ * 4KHz and 11.025KHz are most commonly used in audio applications.
+ * Typically, slimbus runs at frequencies to support channels running at 4KHz
+ * and/or 11.025KHz isochronously.
+ */
+enum slim_ch_rate {
+ SLIM_RATE_1HZ,
+ SLIM_RATE_4000HZ,
+ SLIM_RATE_11025HZ,
+};
+
+/*
+ * enum slim_ch_coeff: Coefficient of a channel used internally by framework.
+ * Coefficient is applicable to channels running isochronously.
+ * Coefficient is calculated based on channel rate multiplier.
+ * (If rate multiplier is power of 2, it's coeff.1 channel. Otherwise it's
+ * coeff.3 channel.
+ */
+enum slim_ch_coeff {
+ SLIM_COEFF_1,
+ SLIM_COEFF_3,
+};
+
+/*
+ * enum slim_ch_control: Channel control.
+ * Activate will schedule channel and/or group of channels in the TDM frame.
+ * Suspend will keep the schedule but data-transfer won't happen.
+ * Remove will remove the channel/group from the TDM frame.
+ */
+enum slim_ch_control {
+ SLIM_CH_ACTIVATE,
+ SLIM_CH_SUSPEND,
+ SLIM_CH_REMOVE,
+};
+
+/* enum slim_ch_dataf: Data format per table 60 from slimbus spec 1.01.01 */
+enum slim_ch_dataf {
+ SLIM_CH_DATAF_NOT_DEFINED = 0,
+ SLIM_CH_DATAF_LPCM_AUDIO = 1,
+ SLIM_CH_DATAF_IEC61937_COMP_AUDIO = 2,
+ SLIM_CH_DATAF_PACKED_PDM_AUDIO = 3,
+};
+
+/* enum slim_ch_auxf: Auxiliary field format per table 59 from slimbus spec */
+enum slim_ch_auxf {
+ SLIM_CH_AUXF_NOT_APPLICABLE = 0,
+ SLIM_CH_AUXF_ZCUV_TUNNEL_IEC60958 = 1,
+ SLIM_CH_USER_DEFINED = 0xF,
+};
+
+/*
+ * struct slim_ch: Channel structure used externally by users of channel APIs.
+ * @prot: Desired slimbus protocol.
+ * @baser: Desired base rate. (Typical isochronous rates are: 4KHz, or 11.025KHz
+ * @dataf: Data format.
+ * @auxf: Auxiliary format.
+ * @ratem: Channel rate multiplier. (e.g. 48KHz channel will have 4KHz base rate
+ * and 12 as rate multiplier.
+ * @sampleszbits: Sample size in bits.
+ */
+struct slim_ch {
+ enum slim_ch_proto prot;
+ enum slim_ch_rate baser;
+ enum slim_ch_dataf dataf;
+ enum slim_ch_auxf auxf;
+ u32 ratem;
+ u32 sampleszbits;
+};
+
+/*
+ * struct slim_ich: Internal channel structure used by slimbus framework.
+ * @prop: structure passed by the client.
+ * @coeff: Coefficient of this channel.
+ * @state: Current state of the channel.
+ * @nextgrp: If this channel is part of group, next channel in this group.
+ * @prrate: Presence rate of this channel (per table 62 of the spec)
+ * @offset: Offset of this channel in the superframe.
+ * @newoff: Used during scheduling to hold temporary new offset until the offset
+ * is accepted/rejected by slimbus reconfiguration.
+ * @interval: Interval of this channel per superframe.
+ * @newintr: Used during scheduling to new interval temporarily.
+ * @seglen: Segment length of this channel.
+ * @rootexp: root exponent of this channel. Rate can be found using rootexp and
+ * coefficient. Used during scheduling.
+ * @srch: Source port used by this channel.
+ * @sinkh: Sink ports used by this channel.
+ * @nsink: number of sink ports used by this channel.
+ * @chan: Channel number sent on hardware lines for this channel. May not be
+ * equal to array-index into chans if client requested to use number beyond
+ * channel-array for the controller.
+ * @ref: Reference number to keep track of how many clients (upto 2) are using
+ * this channel.
+ * @def: Used to keep track of how many times the channel definition is sent
+ * to hardware and this will decide if channel-remove can be sent for the
+ * channel. Channel definition may be sent upto twice (once per producer
+ * and once per consumer). Channel removal should be sent only once to
+ * avoid clients getting underflow/overflow errors.
+ */
+struct slim_ich {
+ struct slim_ch prop;
+ enum slim_ch_coeff coeff;
+ enum slim_ch_state state;
+ u16 nextgrp;
+ u32 prrate;
+ u32 offset;
+ u32 newoff;
+ u32 interval;
+ u32 newintr;
+ u32 seglen;
+ u8 rootexp;
+ u32 srch;
+ u32 *sinkh;
+ int nsink;
+ u8 chan;
+ int ref;
+ int def;
+};
+
+/*
+ * struct slim_sched: Framework uses this structure internally for scheduling.
+ * @chc3: Array of all active coeffient 3 channels.
+ * @num_cc3: Number of active coeffient 3 channels.
+ * @chc1: Array of all active coeffient 1 channels.
+ * @num_cc1: Number of active coeffient 1 channels.
+ * @subfrmcode: Current subframe-code used by TDM. This is decided based on
+ * requested message bandwidth and current channels scheduled.
+ * @usedslots: Slots used by all active channels.
+ * @msgsl: Slots used by message-bandwidth.
+ * @pending_msgsl: Used to store pending request of message bandwidth (in slots)
+ * until the scheduling is accepted by reconfiguration.
+ * @m_reconf: This mutex is held until current reconfiguration (data channel
+ * scheduling, message bandwidth reservation) is done. Message APIs can
+ * use the bus concurrently when this mutex is held since elemental access
+ * messages can be sent on the bus when reconfiguration is in progress.
+ * @slots: Used for debugging purposes to debug/verify current schedule in TDM.
+ */
+struct slim_sched {
+ struct slim_ich **chc3;
+ int num_cc3;
+ struct slim_ich **chc1;
+ int num_cc1;
+ u32 subfrmcode;
+ u32 usedslots;
+ u32 msgsl;
+ u32 pending_msgsl;
+ struct mutex m_reconf;
+ u8 *slots;
+};
+
+/*
+ * enum slim_clk_state: Slimbus controller's clock state used internally for
+ * maintaining current clock state.
+ * @SLIM_CLK_ACTIVE: Slimbus clock is active
+ * @SLIM_CLK_PAUSE_FAILED: Slimbus controlled failed to go in clock pause.
+ * Hardware-wise, this state is same as active but controller will wait on
+ * completion before making transition to SLIM_CLK_ACTIVE in framework
+ * @SLIM_CLK_ENTERING_PAUSE: Slimbus clock pause sequence is being sent on the
+ * bus. If this succeeds, state changes to SLIM_CLK_PAUSED. If the
+ * transition fails, state changes to SLIM_CLK_PAUSE_FAILED
+ * @SLIM_CLK_PAUSED: Slimbus controller clock has paused.
+ */
+enum slim_clk_state {
+ SLIM_CLK_ACTIVE,
+ SLIM_CLK_ENTERING_PAUSE,
+ SLIM_CLK_PAUSE_FAILED,
+ SLIM_CLK_PAUSED,
+};
+/*
+ * struct slim_controller: Represents manager for a SlimBUS
+ * (similar to 'master' on I2C)
+ * @dev: Device interface to this driver
+ * @nr: Board-specific number identifier for this controller/bus
+ * @list: Link with other slimbus controllers
+ * @name: Name for this controller
+ * @clkgear: Current clock gear in which this bus is running
+ * @min_cg: Minimum clock gear supported by this controller (default value: 1)
+ * @max_cg: Maximum clock gear supported by this controller (default value: 10)
+ * @clk_state: Controller's clock state from enum slim_clk_state
+ * @pause_comp: Signals completion of clock pause sequence. This is useful when
+ * client tries to call slimbus transaction when controller may be entering
+ * clock pause.
+ * @a_framer: Active framer which is clocking the bus managed by this controller
+ * @m_ctrl: Mutex protecting controller data structures (ports, channels etc)
+ * @addrt: Logical address table
+ * @num_dev: Number of active slimbus slaves on this bus
+ * @txnt: Table of transactions having transaction ID
+ * @last_tid: size of the table txnt (can't grow beyond 256 since TID is 8-bits)
+ * @ports: Ports associated with this controller
+ * @nports: Number of ports supported by the controller
+ * @chans: Channels associated with this controller
+ * @nchans: Number of channels supported
+ * @reserved: Reserved channels that controller wants to use internally
+ * Clients will be assigned channel numbers after this number
+ * @sched: scheduler structure used by the controller
+ * @dev_released: completion used to signal when sysfs has released this
+ * controller so that it can be deleted during shutdown
+ * @xfer_msg: Transfer a message on this controller (this can be a broadcast
+ * control/status message like data channel setup, or a unicast message
+ * like value element read/write.
+ * @set_laddr: Setup logical address at laddr for the slave with elemental
+ * address e_addr. Drivers implementing controller will be expected to
+ * send unicast message to this device with its logical address.
+ * @wakeup: This function pointer implements controller-specific procedure
+ * to wake it up from clock-pause. Framework will call this to bring
+ * the controller out of clock pause.
+ * @config_port: Configure a port and make it ready for data transfer. This is
+ * called by framework after connect_port message is sent successfully.
+ * @framer_handover: If this controller has multiple framers, this API will
+ * be called to switch between framers if controller desires to change
+ * the active framer.
+ * @port_xfer: Called to schedule a transfer on port pn. iobuf is physical
+ * address and the buffer may have to be DMA friendly since data channels
+ * will be using data from this buffers without SW intervention.
+ * @port_xfer_status: Called by framework when client calls get_xfer_status
+ * API. Returns how much buffer is actually processed and the port
+ * errors (e.g. overflow/underflow) if any.
+ */
+struct slim_controller {
+ struct device dev;
+ unsigned int nr;
+ struct list_head list;
+ char name[SLIMBUS_NAME_SIZE];
+ int clkgear;
+ int min_cg;
+ int max_cg;
+ enum slim_clk_state clk_state;
+ struct completion pause_comp;
+ struct slim_framer *a_framer;
+ struct mutex m_ctrl;
+ struct slim_addrt *addrt;
+ u8 num_dev;
+ struct slim_msg_txn **txnt;
+ u8 last_tid;
+ struct slim_port *ports;
+ int nports;
+ struct slim_ich *chans;
+ int nchans;
+ u8 reserved;
+ struct slim_sched sched;
+ struct completion dev_released;
+ int (*xfer_msg)(struct slim_controller *ctrl,
+ struct slim_msg_txn *txn);
+ int (*set_laddr)(struct slim_controller *ctrl,
+ struct slim_eaddr *ea, u8 *laddr);
+ int (*wakeup)(struct slim_controller *ctrl);
+ int (*config_port)(struct slim_controller *ctrl,
+ u8 port);
+ int (*framer_handover)(struct slim_controller *ctrl,
+ struct slim_framer *new_framer);
+ int (*port_xfer)(struct slim_controller *ctrl,
+ u8 pn, u8 *iobuf, u32 len,
+ struct completion *comp);
+ enum slim_port_err (*port_xfer_status)(struct slim_controller *ctr,
+ u8 pn, u8 **done_buf, u32 *done_len);
+};
+#define to_slim_controller(d) container_of(d, struct slim_controller, dev)
+
+/*
+ * struct slim_driver: Manage Slimbus generic/slave device driver
+ * @probe: Binds this driver to a slimbus device.
+ * @remove: Unbinds this driver from the slimbus device.
+ * @shutdown: Standard shutdown callback used during powerdown/halt.
+ * @suspend: Standard suspend callback used during system suspend
+ * @resume: Standard resume callback used during system resume
+ * @driver: Slimbus device drivers should initialize name and owner field of
+ * this structure
+ * @id_table: List of slimbus devices supported by this driver
+ */
+struct slim_driver {
+ int (*probe)(struct slim_device *sldev);
+ int (*remove)(struct slim_device *sldev);
+ void (*shutdown)(struct slim_device *sldev);
+ int (*suspend)(struct slim_device *sldev,
+ pm_message_t pmesg);
+ int (*resume)(struct slim_device *sldev);
+
+ struct device_driver driver;
+ const struct slim_device_id *id_table;
+};
+#define to_slim_driver(d) container_of(d, struct slim_driver, driver)
+
+/*
+ * struct slim_pending_ch: List of pending channels used by framework.
+ * @chan: Channel number
+ * @pending: list of channels
+ */
+struct slim_pending_ch {
+ u8 chan;
+ struct list_head pending;
+};
+
+/*
+ * Client/device handle (struct slim_device):
+ * ------------------------------------------
+ * This is the client/device handle returned when a slimbus
+ * device is registered with a controller. This structure can be provided
+ * during register_board_info, or can be allocated using slim_add_device API.
+ * Pointer to this structure is used by client-driver as a handle.
+ * @dev: Driver model representation of the device.
+ * @name: Name of driver to use with this device.
+ * @e_addr: Elemental address of this device.
+ * @driver: Device's driver. Pointer to access routines.
+ * @ctrl: Slimbus controller managing the bus hosting this device.
+ * @laddr: 1-byte Logical address of this device.
+ * @enumerated: Set when this device reports present. laddr is valid if this
+ * field is set.
+ * @wait_enum: This completion is signalled when device reports. During early
+ * probe, device can wait on this object to indicate successful
+ * enumeration. Note that device should only wait if enumerated
+ * flag was not set.
+ * @mark_define: List of channels pending definition/activation.
+ * @mark_suspend: List of channels pending suspend.
+ * @mark_removal: List of channels pending removal.
+ * @sldev_reconf: Mutex to protect the pending data-channel lists.
+ * @pending_msgsl: Message bandwidth reservation request by this client in
+ * slots that's pending reconfiguration.
+ * @cur_msgsl: Message bandwidth reserved by this client in slots.
+ * These 3 lists are managed by framework. Lists are populated when client
+ * calls channel control API without reconfig-flag set and the lists are
+ * emptied when the reconfiguration is done by this client.
+ */
+struct slim_device {
+ struct device dev;
+ char *name;
+ struct slim_eaddr e_addr;
+ struct slim_driver *driver;
+ struct slim_controller *ctrl;
+ u8 laddr;
+ bool enumerated;
+ struct completion wait_enum;
+ struct list_head mark_define;
+ struct list_head mark_suspend;
+ struct list_head mark_removal;
+ struct mutex sldev_reconf;
+ u32 pending_msgsl;
+ u32 cur_msgsl;
+};
+#define to_slim_device(d) container_of(d, struct slim_device, dev)
+
+/*
+ * struct slim_boardinfo: Declare board info for Slimbus device bringup.
+ * @bus_num: Controller number (bus) on which this device will sit.
+ * @slim_slave: Device to be registered with slimbus.
+ */
+struct slim_boardinfo {
+ int bus_num;
+ struct slim_device *slim_slave;
+};
+
+/*
+ * slim_get_logical_addr: Return the logical address of a slimbus device.
+ * @sb: client handle requesting the adddress.
+ * @e_addr: Elemental address of the device.
+ * @laddr: output buffer to store the address
+ * context: can sleep
+ * -EINVAL is returned in case of invalid parameters, and -ENXIO is returned if
+ * the device with this elemental address is not found.
+ */
+
+extern int slim_get_logical_addr(struct slim_device *sb,
+ struct slim_eaddr *e_addr, u8 *laddr);
+
+
+/* Message APIs Unicast message APIs used by slimbus slave drivers */
+
+/*
+ * Message API access routines.
+ * @sb: client handle requesting elemental message reads, writes.
+ * @msg: Input structure for start-offset, number of bytes to read.
+ * @rbuf: data buffer to be filled with values read.
+ * @len: data buffer size
+ * @wbuf: data buffer containing value/information to be written
+ * context: can sleep
+ * Returns:
+ * -EINVAL: Invalid parameters
+ * -ETIMEDOUT: If controller could not complete the request. This may happen if
+ * the bus lines are not clocked, controller is not powered-on, slave with
+ * given address is not enumerated/responding.
+ */
+extern int slim_request_val_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *buf,
+ u8 len);
+extern int slim_request_inf_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *buf,
+ u8 len);
+extern int slim_change_val_element(struct slim_device *sb,
+ struct slim_ele_access *msg,
+ const u8 *buf, u8 len);
+extern int slim_clear_inf_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *buf,
+ u8 len);
+extern int slim_request_change_val_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *rbuf,
+ const u8 *wbuf, u8 len);
+extern int slim_request_clear_inf_element(struct slim_device *sb,
+ struct slim_ele_access *msg, u8 *rbuf,
+ const u8 *wbuf, u8 len);
+
+/*
+ * Broadcast message API:
+ * call this API directly with sbdev = NULL.
+ * For broadcast reads, make sure that buffers are big-enough to incorporate
+ * replies from all logical addresses.
+ * All controllers may not support broadcast
+ */
+extern int slim_xfer_msg(struct slim_controller *ctrl,
+ struct slim_device *sbdev, struct slim_ele_access *msg,
+ u16 mc, u8 *rbuf, const u8 *wbuf, u8 len);
+/* end of message apis */
+
+/* Port management for manager device APIs */
+
+/*
+ * slim_alloc_mgrports: Allocate port on manager side.
+ * @sb: device/client handle.
+ * @req: Port request type.
+ * @nports: Number of ports requested
+ * @rh: output buffer to store the port handles
+ * @hsz: size of buffer storing handles
+ * context: can sleep
+ * This port will be typically used by SW. e.g. client driver wants to receive
+ * some data from audio codec HW using a data channel.
+ * Port allocated using this API will be used to receive the data.
+ * If half-duplex ports are requested, two adjacent ports are allocated for
+ * 1 half-duplex port. So the handle-buffer size should be twice the number
+ * of half-duplex ports to be allocated.
+ * -EDQUOT is returned if all ports are in use.
+ */
+extern int slim_alloc_mgrports(struct slim_device *sb, enum slim_port_req req,
+ int nports, u32 *rh, int hsz);
+
+/* Deallocate the port(s) allocated using the API above */
+extern int slim_dealloc_mgrports(struct slim_device *sb, u32 *hdl, int hsz);
+
+/*
+ * slim_port_xfer: Schedule buffer to be transferred/received using port-handle.
+ * @sb: client handle
+ * @ph: port-handle
+ * @iobuf: buffer to be transferred or populated
+ * @len: buffer size.
+ * @comp: completion signal to indicate transfer done or error.
+ * context: can sleep
+ * Returns number of bytes transferred/received if used synchronously.
+ * Will return 0 if used asynchronously.
+ * Client will call slim_port_get_xfer_status to get error and/or number of
+ * bytes transferred if used asynchronously.
+ */
+extern int slim_port_xfer(struct slim_device *sb, u32 ph, u8 *iobuf, u32 len,
+ struct completion *comp);
+
+/*
+ * slim_port_get_xfer_status: Poll for port transfers, or get transfer status
+ * after completion is done.
+ * @sb: client handle
+ * @ph: port-handle
+ * @done_buf: return pointer (iobuf from slim_port_xfer) which is processed.
+ * @done_len: Number of bytes transferred.
+ * This can be called when port_xfer completion is signalled.
+ * The API will return port transfer error (underflow/overflow/disconnect)
+ * and/or done_len will reflect number of bytes transferred. Note that
+ * done_len may be valid even if port error (overflow/underflow) has happened.
+ * e.g. If the transfer was scheduled with a few bytes to be transferred and
+ * client has not supplied more data to be transferred, done_len will indicate
+ * number of bytes transferred with underflow error. To avoid frequent underflow
+ * errors, multiple transfers can be queued (e.g. ping-pong buffers) so that
+ * channel has data to be transferred even if client is not ready to transfer
+ * data all the time. done_buf will indicate address of the last buffer
+ * processed from the multiple transfers.
+ */
+extern enum slim_port_err slim_port_get_xfer_status(struct slim_device *sb,
+ u32 ph, u8 **done_buf, u32 *done_len);
+
+/*
+ * slim_connect_src: Connect source port to channel.
+ * @sb: client handle
+ * @srch: source handle to be connected to this channel
+ * @chanh: Channel with which the ports need to be associated with.
+ * Per slimbus specification, a channel may have 1 source port.
+ * Channel specified in chanh needs to be allocated first.
+ * Returns -EALREADY if source is already configured for this channel.
+ * Returns -ENOTCONN if channel is not allocated
+ */
+extern int slim_connect_src(struct slim_device *sb, u32 srch, u16 chanh);
+
+/*
+ * slim_connect_sink: Connect sink port(s) to channel.
+ * @sb: client handle
+ * @sinkh: sink handle(s) to be connected to this channel
+ * @nsink: number of sinks
+ * @chanh: Channel with which the ports need to be associated with.
+ * Per slimbus specification, a channel may have multiple sink-ports.
+ * Channel specified in chanh needs to be allocated first.
+ * Returns -EALREADY if sink is already configured for this channel.
+ * Returns -ENOTCONN if channel is not allocated
+ */
+extern int slim_connect_sink(struct slim_device *sb, u32 *sinkh, int nsink,
+ u16 chanh);
+/*
+ * slim_disconnect_ports: Disconnect port(s) from channel
+ * @sb: client handle
+ * @ph: ports to be disconnected
+ * @nph: number of ports.
+ * Disconnects ports from a channel.
+ */
+extern int slim_disconnect_ports(struct slim_device *sb, u32 *ph, int nph);
+
+/*
+ * slim_get_slaveport: Get slave port handle
+ * @la: slave device logical address.
+ * @idx: port index at slave
+ * @rh: return handle
+ * @flw: Flow type (source or destination)
+ * This API only returns a slave port's representation as expected by slimbus
+ * driver. This port is not managed by the slimbus driver. Caller is expected
+ * to have visibility of this port since it's a device-port.
+ */
+extern int slim_get_slaveport(u8 la, int idx, u32 *rh, enum slim_port_flow flw);
+
+
+/* Channel functions. */
+
+/*
+ * slim_alloc_ch: Allocate a slimbus channel and return its handle.
+ * @sb: client handle.
+ * @chanh: return channel handle
+ * Slimbus channels are limited to 256 per specification.
+ * -EXFULL is returned if all channels are in use.
+ * Although slimbus specification supports 256 channels, a controller may not
+ * support that many channels.
+ */
+extern int slim_alloc_ch(struct slim_device *sb, u16 *chanh);
+
+/*
+ * slim_query_ch: Get reference-counted handle for a channel number. Every
+ * channel is reference counted by one as producer and the others as
+ * consumer)
+ * @sb: client handle
+ * @chan: slimbus channel number
+ * @chanh: return channel handle
+ * If request channel number is not in use, it is allocated, and reference
+ * count is set to one. If the channel was was already allocated, this API
+ * will return handle to that channel and reference count is incremented.
+ * -EXFULL is returned if all channels are in use
+ */
+extern int slim_query_ch(struct slim_device *sb, u8 chan, u16 *chanh);
+/*
+ * slim_dealloc_ch: Deallocate channel allocated using the API above
+ * -EISCONN is returned if the channel is tried to be deallocated without
+ * being removed first.
+ * -ENOTCONN is returned if deallocation is tried on a channel that's not
+ * allocated.
+ */
+extern int slim_dealloc_ch(struct slim_device *sb, u16 chanh);
+
+
+/*
+ * slim_define_ch: Define a channel.This API defines channel parameters for a
+ * given channel.
+ * @sb: client handle.
+ * @prop: slim_ch structure with channel parameters desired to be used.
+ * @chanh: list of channels to be defined.
+ * @nchan: number of channels in a group (1 if grp is false)
+ * @grp: Are the channels grouped
+ * @grph: return group handle if grouping of channels is desired.
+ * Channels can be grouped if multiple channels use same parameters
+ * (e.g. 5.1 audio has 6 channels with same parameters. They will all be
+ * grouped and given 1 handle for simplicity and avoid repeatedly calling
+ * the API)
+ * -EISCONN is returned if channel is already used with different parameters.
+ * -ENXIO is returned if the channel is not yet allocated.
+ */
+extern int slim_define_ch(struct slim_device *sb, struct slim_ch *prop,
+ u16 *chanh, u8 nchan, bool grp, u16 *grph);
+
+/*
+ * slim_control_ch: Channel control API.
+ * @sb: client handle
+ * @grpchanh: group or channel handle to be controlled
+ * @chctrl: Control command (activate/suspend/remove)
+ * @commit: flag to indicate whether the control should take effect right-away.
+ * This API activates, removes or suspends a channel (or group of channels)
+ * grpchanh indicates the channel or group handle (returned by the define_ch
+ * API). Reconfiguration may be time-consuming since it can change all other
+ * active channel allocations on the bus, change in clock gear used by the
+ * slimbus, and change in the control space width used for messaging.
+ * commit makes sure that multiple channels can be activated/deactivated before
+ * reconfiguration is started.
+ * -EXFULL is returned if there is no space in TDM to reserve the bandwidth.
+ * -EISCONN/-ENOTCONN is returned if the channel is already connected or not
+ * yet defined.
+ * -EINVAL is returned if individual control of a grouped-channel is attempted.
+ */
+extern int slim_control_ch(struct slim_device *sb, u16 grpchanh,
+ enum slim_ch_control chctrl, bool commit);
+
+/*
+ * slim_get_ch_state: Channel state.
+ * This API returns the channel's state (active, suspended, inactive etc)
+ */
+extern enum slim_ch_state slim_get_ch_state(struct slim_device *sb,
+ u16 chanh);
+
+/*
+ * slim_reservemsg_bw: Request to reserve bandwidth for messages.
+ * @sb: client handle
+ * @bw_bps: message bandwidth in bits per second to be requested
+ * @commit: indicates whether the reconfiguration needs to be acted upon.
+ * This API call can be grouped with slim_control_ch API call with only one of
+ * the APIs specifying the commit flag to avoid reconfiguration being called too
+ * frequently. -EXFULL is returned if there is no space in TDM to reserve the
+ * bandwidth. -EBUSY is returned if reconfiguration is requested, but a request
+ * is already in progress.
+ */
+extern int slim_reservemsg_bw(struct slim_device *sb, u32 bw_bps, bool commit);
+
+/*
+ * slim_reconfigure_now: Request reconfiguration now.
+ * @sb: client handle
+ * This API does what commit flag in other scheduling APIs do.
+ * -EXFULL is returned if there is no space in TDM to reserve the
+ * bandwidth. -EBUSY is returned if reconfiguration request is already in
+ * progress.
+ */
+extern int slim_reconfigure_now(struct slim_device *sb);
+
+/*
+ * slim_ctrl_clk_pause: Called by slimbus controller to request clock to be
+ * paused or woken up out of clock pause
+ * @ctrl: controller requesting bus to be paused or woken up
+ * @wakeup: Wakeup this controller from clock pause.
+ * @restart: Restart time value per spec used for clock pause. This value
+ * isn't used when controller is to be woken up.
+ * This API executes clock pause reconfiguration sequence if wakeup is false.
+ * If wakeup is true, controller's wakeup is called
+ * Slimbus clock is idle and can be disabled by the controller later.
+ */
+extern int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup,
+ u8 restart);
+
+/*
+ * slim_driver_register: Client driver registration with slimbus
+ * @drv:Client driver to be associated with client-device.
+ * This API will register the client driver with the slimbus
+ * It is called from the driver's module-init function.
+ */
+extern int slim_driver_register(struct slim_driver *drv);
+
+/*
+ * slim_add_numbered_controller: Controller bring-up.
+ * @ctrl: Controller to be registered.
+ * A controller is registered with the framework using this API. ctrl->nr is the
+ * desired number with which slimbus framework registers the controller.
+ * Function will return -EBUSY if the number is in use.
+ */
+extern int slim_add_numbered_controller(struct slim_controller *ctrl);
+
+/*
+ * slim_del_controller: Controller tear-down.
+ * Controller added with the above API is teared down using this API.
+ */
+extern int slim_del_controller(struct slim_controller *ctrl);
+
+/*
+ * slim_add_device: Add a new device without register board info.
+ * @ctrl: Controller to which this device is to be added to.
+ * Called when device doesn't have an explicit client-driver to be probed, or
+ * the client-driver is a module installed dynamically.
+ */
+extern int slim_add_device(struct slim_controller *ctrl,
+ struct slim_device *sbdev);
+
+/*
+ * slim_query_device: Query and get handle to a device.
+ * @ctrl: Controller on which this device will be added/queried
+ * @e_addr: Elemental address of the device to be queried
+ * Returns pointer to a device if it has already reported. Creates a new
+ * device and returns pointer to it if the device has not yet enumerated.
+ */
+struct slim_device *slim_query_device(struct slim_controller *ctrl,
+ struct slim_eaddr *e_addr);
+
+/* slim_remove_device: Remove the effect of slim_add_device() */
+extern void slim_remove_device(struct slim_device *sbdev);
+
+/*
+ * slim_assign_laddr: Assign logical address to a device enumerated.
+ * @ctrl: Controller with which device is enumerated.
+ * @e_addr: Elemental address of the device.
+ * @laddr: Return logical address.
+ * Called by controller in response to REPORT_PRESENT. Framework will assign
+ * a logical address to this enumeration address.
+ * Function returns -EXFULL to indicate that all logical addresses are already
+ * taken.
+ */
+extern int slim_assign_laddr(struct slim_controller *ctrl,
+ struct slim_eaddr *e_addr, u8 *laddr);
+
+/*
+ * slim_msg_response: Deliver Message response received from a device to the
+ * framework.
+ * @ctrl: Controller handle
+ * @reply: Reply received from the device
+ * @len: Length of the reply
+ * @tid: Transaction ID received with which framework can associate reply.
+ * Called by controller to inform framework about the response received.
+ * This helps in making the API asynchronous, and controller-driver doesn't need
+ * to manage 1 more table other than the one managed by framework mapping TID
+ * with buffers
+ */
+extern void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid,
+ u8 len);
+
+/*
+ * slim_register_board_info: Board-initialization routine.
+ * @info: List of all devices on all controllers present on the board.
+ * @n: number of entries.
+ * API enumerates respective devices on corresponding controller.
+ * Called from board-init function.
+ */
+#ifdef CONFIG_SLIMBUS
+extern int slim_register_board_info(struct slim_boardinfo const *info,
+ unsigned n);
+#else
+int slim_register_board_info(struct slim_boardinfo const *info,
+ unsigned n)
+{
+ return 0;
+}
+#endif
+
+static inline void *slim_get_ctrldata(const struct slim_controller *dev)
+{
+ return dev_get_drvdata(&dev->dev);
+}
+
+static inline void slim_set_ctrldata(struct slim_controller *dev, void *data)
+{
+ dev_set_drvdata(&dev->dev, data);
+}
+
+static inline void *slim_get_devicedata(const struct slim_device *dev)
+{
+ return dev_get_drvdata(&dev->dev);
+}
+
+static inline void slim_set_clientdata(struct slim_device *dev, void *data)
+{
+ dev_set_drvdata(&dev->dev, data);
+}
+#endif /* _LINUX_SLIMBUS_H */
--
1.7.9
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
On Tue, May 29, 2012 at 07:11:33PM -0600, Sagar Dharia wrote:
> SLIMbus (Serial Low Power Interchip Media Bus) is a specification
> developed by MIPI (Mobile Industry Processor Interface) alliance.
This patch is about 150k which is rather large to review. It would be
enormously helpful for review if it could be broken down into a patch
series. For example, things like the device registration could be
broken out into a separate patch as could the core bus operations and
things like the clocking/bandwidth management.
I've not actually looked through the patch yet, partly because doing it
all in one sitting will take some time.
On Tue, May 29, 2012 at 07:11:33PM -0600, Sagar Dharia wrote:
> SLIMbus (Serial Low Power Interchip Media Bus) is a specification
> developed by MIPI (Mobile Industry Processor Interface) alliance.
These are initial comments. A full review of the patch will take some
time. While I am posting this email, this is the result of work both
by myself, and my colleague Greg Clemson (cc'ed).
1. enum slim_ch_proto
The enumeration slim_ch_proto is incorrect. It declares 2 transport
protocols which do not exist in the specification: SLIM_HARD_ISO;
SLIM_AUTO_ISO.
This in turn causes the subsequent values for the other protocols such
as SLIM_PUSH, to be incorrectly coded in the NEXT_DEFINE_CHANNEL
message generated in slim_reconfigure_now(). That is the push protocol
is incorrectly encoded as 3 instead of 2 as per the specification
(Table 47).
I believe a correct definition would be:
enum slim_ch_proto {
SLIM_ISO,
SLIM_PUSH,
SLIM_PULL,
SLIM_LOCKED,
SLIM_ASYNC_SMPLX,
SLIM_ASYNC_HALF_DUP,
SLIM_EXT_SMPLX,
SLIM_EXT_HALF_DUP,
SLIM_USER_DEF1 = 14,
SLIM_USER_DEF2 = 15
};
However doing so will break the logic in slim_nextdefine_ch().
2. Use of the term elemental address.
Commentary around the use of struct slim_eaddr uses the term
"elemental address". However, the term used in the specification is
"enumeration address". There is no elemental address in the
specification, and using this term may result confusion when referring
to accessing the information and value elements.
I suggest that term "enumeration address" should be used.
3. Probing sequence for drivers.
The current probing sequence for drivers appears to make some
assumptions, that are problematic.
a) The code assumes that when the controller is registered the bus is
operational (booted). No allowance is made for any problems the active
framer may have synchronizing the bus.
The drivers really should not be probed until the bus has at least
booted. This can be worked around by only registering the controller
after it has booted the bus. This should be noted in the comments.
b) Similarly to (a) the driver may be probed before the device has
been given a logical address (LA). This makes sense in the case of
driver that turns on the device (say via gpio) once the bus has
booted. However, the driver then needs to sit and poll
slim_get_logical_addr() until the logical address.
A possible alternate solution would be to add a parameter to probe:
enum slim_dev_status {
SLIM_BUS_READY,
SLIM_DEV_READY
};
int (*probe)(struct slim_device *sldev, enum slim_dev_status status);
Where SLIM_BUS_READY == bus has booted, and SLIM_DEV_READY == device
has been assigned its logical address.
Alternatively you could have 2 separate probe type callbacks.
This would make the driver logical simpler.
On Tue, 2012-05-29 at 19:11 -0600, Sagar Dharia wrote:
> SLIMbus (Serial Low Power Interchip Media Bus) is a specification
> developed by MIPI (Mobile Industry Processor Interface) alliance.
Just a few trivial notes before stopped reading as it was
very long.
> diff --git a/drivers/of/of_slimbus.c b/drivers/of/of_slimbus.c
[]
> + name = kzalloc(SLIMBUS_NAME_SIZE, GFP_KERNEL);
> + if (!name) {
> + dev_err(&ctrl->dev, "of_slim: out of memory");
OOM messages aren't necessary as they are reported
with a dump_stack in the alloc functions and please
make sure all messages are newline terminated.
[]
> + binfo = krealloc(binfo, (n + 1) * sizeof(struct slim_boardinfo),
> + GFP_KERNEL);
Reallocs should _always_ use a new temporary.
Otherwise, there will be a memory leak of the
original pointer memory on failure.
> diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c
[]
> + if (ctrl->nchans) {
> + ctrl->chans = kzalloc(ctrl->nchans * sizeof(struct slim_ich),
> + GFP_KERNEL);
please use kcalloc where appropriate.
> + ctrl->sched.chc1 =
> + kzalloc(ctrl->nchans * sizeof(struct slim_ich *),
> + GFP_KERNEL);
[]
> + ctrl->sched.chc3 =
> + kzalloc(ctrl->nchans * sizeof(struct slim_ich *),
> + GFP_KERNEL);
kcalloc...
> + ctrl->txnt = krealloc(ctrl->txnt,
> + (i + 1) * sizeof(struct slim_msg_txn *),
> + GFP_KERNEL);
that realloc ptr use again.
> +int slim_assign_laddr(struct slim_controller *ctrl, struct slim_eaddr *e_addr,
> + u8 *laddr)
[]
> + ctrl->addrt = krealloc(ctrl->addrt,
> + (ctrl->num_dev + 1) *
> + sizeof(struct slim_addrt),
> + GFP_KERNEL);
and again, I won't look for it again.
> +static u16 slim_slicesize(u32 code)
> +{
> + static const u8 sizetocode[16] = {0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6,
> + 6, 6, 7};
Perhaps more style conformant as:
static const u8 sizetocode[16] = {
0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7
};
> + if (code == 0)
> + code = 1;
> + if (code > ARRAY_SIZE(sizetocode))
> + code = 16;
clamp()
Sorry for taking so long, this is really huge and I get distracted every
5 minutes with other things. Also, you cc'd so many different people and
lists with this I'm reluctant to "reply all"...
I just care about the documentation here, so:
On 05/29/2012 08:11 PM, Sagar Dharia wrote:
> SLIMbus (Serial Low Power Interchip Media Bus) is a specification
> developed by MIPI (Mobile Industry Processor Interface) alliance.
> SLIMbus is a 2-wire implementation, which is used to communicate with
> peripheral components like audio. Commonly used digital audio
> interfaces such as I2S, PCM are intended for point-to-point connection
> between application processor and single audio device and support one
> or two channels. Adding more channels or functions is difficult
> without increasing number of bus structures and hence pin count.
> In parallel to audio channels, control buses such as I2C are typically
> used for low-bandwidth control tasks.
> SLIMbus replaces many digital audio buses and control buses by
> providing flexible and dynamic bus-bandwidth between data-functions
> and control-functions.
So that's a really long way of saying "A bit like I2C but faster"?
> The framework supports message APIs, channel scheduling for SLIMbus.
> Message APIs are used for status/control type of communication with a
> device. Data Channel APIs are used for setting data channels between
> SLIMbus devices.
>
> Framework supports multiple busses (1 controller per bus) and multiple
> clients/slave devices per controller.
>
> Signed-off-by: Sagar Dharia <[email protected]>
> ---
> Documentation/slimbus/slimbus-framework.txt | 319 +++
> drivers/Kconfig | 2 +
> drivers/Makefile | 1 +
> drivers/of/of_slimbus.c | 89 +
> drivers/slimbus/Kconfig | 10 +
> drivers/slimbus/Makefile | 4 +
> drivers/slimbus/slimbus.c | 3050 +++++++++++++++++++++++++++
> include/linux/mod_devicetable.h | 7 +
> include/linux/of_slimbus.h | 34 +
> include/linux/slimbus/slimbus.h | 1072 ++++++++++
Slim?
methinks-the-lady-doth-protest-too-much-bus.
> diff --git a/Documentation/slimbus/slimbus-framework.txt b/Documentation/slimbus/slimbus-framework.txt
> new file mode 100644
> index 0000000..8c27597
> --- /dev/null
> +++ b/Documentation/slimbus/slimbus-framework.txt
> @@ -0,0 +1,319 @@
> +Introduction
> +============
> +
> +Slimbus (Serial Low Power Interchip Media Bus) is a specification developed by
> +MIPI (Mobile Industry Processor Interface) alliance. Slimbus is a 2-wire
> +implementation, which is used to communicate with peripheral components like
> +audio.
Fine so far.
> Commonly used digital audio interfaces such as I2S and PCM are intended
> +for point-to-point connection between application processor and single audio
> +device and support one or two channels. Adding more channels or functions is
> +difficult without increasing number of bus structures and hence pin count.
> +In parallel to audio channels, control buses such as I2C are typically used for
> +low-bandwidth control tasks.
> +
> +Slimbus replaces many digital audio buses and control buses by providing
> +flexible and dynamic bus-bandwidth between data-functions and control-functions.
This seems like an advertisement for your hardware choice. You really
don't have to compare yourself to other busses here, we're not hardware
engineers deciding what wires to run. We just want to know how to
program it.
I'm all for a document describing _your_ bus, but the above isn't it.
> +Hardware Description
> +====================
> +
> +Slimbus is a 2-wire multi-drop interface and uses TDM frame structure.
> +The devices communicate over a shared bus using predefined transport protocols.
> +There are 2 different type of transports.
> +
> +The message transport is used for various control functions such as bus
> +management, configuration and status updates. Messages are seen by all devices
> +and the messages can be of unicast and broadcast type. E.g. reading/writing
> +device specific values is typically a unicast message. A data channel
> +reconfiguration sequence is announced to all devices using a broadcast message.
Unicast messages are seen by all devices?
> +A data transport is used for data-transfer between 2 Slimbus devices. Data
> +transport uses dedicated ports on the device. This data-transfer is not seen
> +by all devices.
So data transport only offers unicast, then? Or does "seen by" here mean
that control messages (excuse me, "message messages") are transported in
an electrically different manner that allows any piece of hardware to
snoop on them, although it should never actually react to ones that
aren't addressed to it?
> +Slimbus specification has different types of device classifications based on
> +their capabilities.
I'm trying to figure out if that sentence conveys any information at all.
> A manager device is responsible for enumeration,
> +configuration, and dynamic channel allocation. Every bus has 1 active manager.
> +A framer device is responsible for driving the clock line and placing
> +information on the data line.
It's nice to know that the 2 wires in the design are 'clock line' and
'data line'. Bit of reading to get to that point...
> Multiple framers may exist on a bus and a manager
> +is responsible for doing framer-handoffs.
> +(Not supported in the SW driver right now).
So each slimbus has 1 manager device that enumerates configures, and
allocates channels for the other devices on the bus. The rest are
"framer" devices that transmit data packets?
> +Per specification,
Ah, I'd assumed that everything in here was in flagrant violation of the
specification. Good to know.
> Slimbus uses "clock gears" to do power management based on
> +current frequency and bandwidth requirements. There are 10 clock gears and each
> +gear changes the Slimbus frequency to be twice its previous gear.
Ok, dynamic clock. Might have been nice to put it in the description.
(Nice of them to choose a range that _wasn't_ a power of 2...)
The base gear is what clock frequency?
> +A generic device is a device providing Slimbus functionality and typically has
> +ports for data-channel support. Its registers are mapped as 'value elements'
> +so that they can be written/read using Slimbus message transport for
> +exchanging control/status type of information. Generic devices are also referred
> +to as 'clients' in this document from here onward.
My eyes glazed over at "Its registers are mapped as 'value elements'"
and I started wondering if the API synergized their core competencies.
It would be nice if you could pick a name for things and stick with it.
> +Each device has a 6-byte enumeration-address and the manager assigns every
> +device with a 1-byte logical address after the devices report present on the
> +bus.
> +
> +Slimbus uses TDM framing at the physical layer and has a variable number of
> +frames used for messaging and data-channels. The TDM framing is out of scope
> +of this document since clients cannot directly access or interfere with it.
THEN WHY DO YOU EVEN MENTION IT HERE?
Please tell us how to program the hardware. Don't try to sell us on
Qualcomm's design choices. (I assume codeaurora is still qualcomm
pretending not to be, because Quic wasn't enough of a potholder for open
source to satisfy the lawyers who run the place? Doesn't matter...)
You refer to "per specification" a lot. Is the specification online,
and if so would you like to link to it?
The weekend's over and this is as far as I managed to read. I think
it's as far as I'm going to. Please add a link to the spec and I'll ACK
the doc even if it does seem more concerned with convincing people to
use the technology than explaining how to program it. (If we NAKed
documentation for being badly written we wouldn't have much.)
However, I expect some example code would be far more useful than this
document...
Rob
--
GNU/Linux isn't: Linux=GPLv2, GNU=GPLv3+, they can't share code.
Either it's "mere aggregation", or a license violation. Pick one.
On Sun, Jun 03, 2012 at 10:14:20PM -0500, Rob Landley wrote:
> On 05/29/2012 08:11 PM, Sagar Dharia wrote:
> > SLIMbus replaces many digital audio buses and control buses by
> > providing flexible and dynamic bus-bandwidth between data-functions
> > and control-functions.
> So that's a really long way of saying "A bit like I2C but faster"?
No, it's replacing I2S (or whatever) too. Really it's much more like
USB than I2C.
>
> Slim?
>
SLIMbus is MIPI alliance specification. Since word "bus" is part of the
specification, I thought it will be good to keep it that way.
The specification is available to all MIPI members.
Some more info here:
http://mipi.org/specifications/serial-low-power-inter-chip-media-bus-slimbussm-specification
>> +and the messages can be of unicast and broadcast type. E.g.
>> reading/writing
>> +device specific values is typically a unicast message. A data channel
>> +reconfiguration sequence is announced to all devices using a broadcast
>> message.
>
> Unicast messages are seen by all devices?
>
I was trying to give example of each type of both message. I will reword
it to be more clear.
>> +Slimbus specification has different types of device classifications
>> based on
>> +their capabilities.
>
> I'm trying to figure out if that sentence conveys any information at all.
>
I will reword this to say: "has following type of device classifications
based on capabilities:".
> So each slimbus has 1 manager device that enumerates configures, and
> allocates channels for the other devices on the bus. The rest are
> "framer" devices that transmit data packets?
>
Typically there is 1 framer per bus, but if there are multiple framers
then manager is responsible for doing handoff between framers.
>
> However, I expect some example code would be far more useful than this
> document...
>
Please refer to slimbus.c for slimbus framework.
Thank you for your comments. I will address all the comments in the next
patch.
Regards
Sagar
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
> This patch is about 150k which is rather large to review. It would be
> enormously helpful for review if it could be broken down into a patch
> series. For example, things like the device registration could be
> broken out into a separate patch as could the core bus operations and
> things like the clocking/bandwidth management.
>
Thank you Mark for looking at the patch.
It's a good idea to break the patch. I am on vacation right now till
6/15/12. I will work on breaking in logical pieces when I get back.
Regards
Sagar
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
> These are initial comments. A full review of the patch will take some
> time. While I am posting this email, this is the result of work both
> by myself, and my colleague Greg Clemson (cc'ed).
>
Thank you Marc and Greg for taking time to review this.
> The enumeration slim_ch_proto is incorrect. It declares 2 transport
> protocols which do not exist in the specification: SLIM_HARD_ISO;
> SLIM_AUTO_ISO.
The enums are more SW representation (and not 1-1 mapping). Difference
between HARD_ISO and AUTO_ISO with example:
Let's say the root frequency is 24.576MHz. then all 4K family channels
(sample rate multiple of 4K) can run isochronously, and all 11.025KHz
channel can run with good efficiency.
So if a client wants 11.025KHz and is does not want to do flow-control at
this root frequency, then the client can specify AUTO_ISO to get the next
available isochronous frequency.
> I suggest that term "enumeration address" should be used.
>
Thank you for catching this. I will change it where-ever applicable.
> b) Similarly to (a) the driver may be probed before the device has
> been given a logical address (LA). This makes sense in the case of
> driver that turns on the device (say via gpio) once the bus has
> booted. However, the driver then needs to sit and poll
> slim_get_logical_addr() until the logical address.
This is not the case anymore.
While taking care of the comments for RFC, I have introduced a completion
that will be signalled when LA is given to the device. The driver can wait
on that completion (wait_enum) instead of polling.
Regards
Sagar
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
> Reallocs should _always_ use a new temporary.
> Otherwise, there will be a memory leak of the
> original pointer memory on failure.
>
Thank you Joe for your comments. I will change the allocs, take care of
the styling based on your comments when I split the patch and resubmit.
Regards
Sagar
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
On Mon, Jun 04, 2012 at 03:21:06AM -0700, Sagar Dharia wrote:
> > b) Similarly to (a) the driver may be probed before the device has
> > been given a logical address (LA). This makes sense in the case of
> > driver that turns on the device (say via gpio) once the bus has
> > booted. However, the driver then needs to sit and poll
> > slim_get_logical_addr() until the logical address.
> This is not the case anymore.
> While taking care of the comments for RFC, I have introduced a completion
> that will be signalled when LA is given to the device. The driver can wait
> on that completion (wait_enum) instead of polling.
This would mean that the thread the probe is happening in will be
blocked until the LA is assigned. That sounds like it might cause
problems, either slowing things down or worst case causing a deadlock.
>> While taking care of the comments for RFC, I have introduced a
>> completion
>> that will be signalled when LA is given to the device. The driver can
>> wait
>> on that completion (wait_enum) instead of polling.
>
> This would mean that the thread the probe is happening in will be
> blocked until the LA is assigned. That sounds like it might cause
> problems, either slowing things down or worst case causing a deadlock.
>
I would expect that the slim_device's driver will only power-on the device
during probe (and use wait_for_completion during 1st transfer to get LA).
Typically transfers are not done as part of probe. Even if transfers need
to be done as part of probe, I expect wait_for_completion (with timeout to
avoid potential HW problems causing linux probe to wait forever) will be
better than polling for get_logical_addr.
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
On Mon, Jun 04, 2012 at 03:36:55AM -0700, Sagar Dharia wrote:
> I would expect that the slim_device's driver will only power-on the device
> during probe (and use wait_for_completion during 1st transfer to get LA).
> Typically transfers are not done as part of probe. Even if transfers need
I'd really expect that we'll have devices that we want to interact with
during probe, for example to determine the device variant. I'd also
expect that it'd be useful to defer things like registering the device
with higher level APIs until we've actually got it up and running, it
tends to make life simpler.
> to be done as part of probe, I expect wait_for_completion (with timeout to
> avoid potential HW problems causing linux probe to wait forever) will be
> better than polling for get_logical_addr.
I agree that a completion is better if we have to block in the device
driver, but the idea someone suggested of having the framework generate
a second callback when it's assigned a logical address seems even
better.
On Mon, Jun 04, 2012 at 03:21:06AM -0700, Sagar Dharia wrote:
> > The enumeration slim_ch_proto is incorrect. It declares 2 transport
> > protocols which do not exist in the specification: SLIM_HARD_ISO;
> > SLIM_AUTO_ISO.
>
> The enums are more SW representation (and not 1-1 mapping). Difference
> between HARD_ISO and AUTO_ISO with example:
> Let's say the root frequency is 24.576MHz. then all 4K family channels
> (sample rate multiple of 4K) can run isochronously, and all 11.025KHz
> channel can run with good efficiency.
> So if a client wants 11.025KHz and is does not want to do flow-control at
> this root frequency, then the client can specify AUTO_ISO to get the next
> available isochronous frequency.
I understand it is not a 1-to-1 mapping. However it *is* used as such:
wbuf[2] = (u8)((segdist & 0xF00) >> 8) | (slc->prop.prot << 4);
which results in NEXT_DEFINE_CHANNEL messages with an invalid TP
field.
More importantly I think it makes it harder to understand the
framework. The concept of HARD_ISO and AUTO_ISO are extensions, and
should really look like such, say perhaps:
enum {
SLIM_ISO,
SLIM_PUSH,
SLIM_PULL,
SLIM_LOCKED,
SLIM_ASYNC_SMPLX,
SLIM_ASYNC_HALF_DUP,
SLIM_EXT_SMPLX,
SLIM_EXT_HALF_DUP,
SLIM_AUTO_ISO,
SLIM_USER_DEF_1 = 14,
SLIM_USER_DEF_2 = 15,
/* extensions */
SLIM_HARD_ISO,
SLIM_AUTO_ISO
};
> > b) Similarly to (a) the driver may be probed before the device has
> > been given a logical address (LA). This makes sense in the case of
> > driver that turns on the device (say via gpio) once the bus has
> > booted. However, the driver then needs to sit and poll
> > slim_get_logical_addr() until the logical address.
> This is not the case anymore.
> While taking care of the comments for RFC, I have introduced a completion
> that will be signalled when LA is given to the device. The driver can wait
> on that completion (wait_enum) instead of polling.
Yes, my mistake. The driver wouldn't have to poll if there was another
callback. So I don't see how the completion mechanism is superior: it
forces a synchronous interface to asynchronous events, or the driver
developer has to work around it.
--
If you wake up and you're not in pain, you know you're dead.
(Russian Proverb)
On 30/05/12 11:11, Sagar Dharia wrote:
> SLIMbus (Serial Low Power Interchip Media Bus) is a specification
> developed by MIPI (Mobile Industry Processor Interface) alliance.
> SLIMbus is a 2-wire implementation, which is used to communicate with
> peripheral components like audio. Commonly used digital audio
> interfaces such as I2S, PCM are intended for point-to-point connection
> between application processor and single audio device and support one
> or two channels. Adding more channels or functions is difficult
> without increasing number of bus structures and hence pin count.
> In parallel to audio channels, control buses such as I2C are typically
> used for low-bandwidth control tasks.
> SLIMbus replaces many digital audio buses and control buses by
> providing flexible and dynamic bus-bandwidth between data-functions
> and control-functions.
>
> The framework supports message APIs, channel scheduling for SLIMbus.
> Message APIs are used for status/control type of communication with a
> device. Data Channel APIs are used for setting data channels between
> SLIMbus devices.
>
> Framework supports multiple busses (1 controller per bus) and multiple
> clients/slave devices per controller.
Hi Sagar. A few comments below. I have only done a quick read through.
~Ryan
>
> Signed-off-by: Sagar Dharia <[email protected]>
> ---
> Documentation/slimbus/slimbus-framework.txt | 319 +++
> drivers/Kconfig | 2 +
> drivers/Makefile | 1 +
> drivers/of/of_slimbus.c | 89 +
> drivers/slimbus/Kconfig | 10 +
> drivers/slimbus/Makefile | 4 +
> drivers/slimbus/slimbus.c | 3050 +++++++++++++++++++++++++++
> include/linux/mod_devicetable.h | 7 +
> include/linux/of_slimbus.h | 34 +
> include/linux/slimbus/slimbus.h | 1072 ++++++++++
> 10 files changed, 4588 insertions(+), 0 deletions(-)
> create mode 100644 Documentation/slimbus/slimbus-framework.txt
> create mode 100644 drivers/of/of_slimbus.c
> create mode 100644 drivers/slimbus/Kconfig
> create mode 100644 drivers/slimbus/Makefile
> create mode 100644 drivers/slimbus/slimbus.c
> create mode 100644 include/linux/of_slimbus.h
> create mode 100644 include/linux/slimbus/slimbus.h
<snip>
> diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c
> new file mode 100644
> index 0000000..6d20241
> --- /dev/null
> +++ b/drivers/slimbus/slimbus.c
> @@ -0,0 +1,3050 @@
> +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/errno.h>
> +#include <linux/slab.h>
> +#include <linux/init.h>
> +#include <linux/completion.h>
> +#include <linux/idr.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slimbus/slimbus.h>
> +
> +#define SLIM_PORT_HDL(la, f, p) ((la)<<24 | (f) << 16 | (p))
> +
> +#define SLIM_HDL_TO_LA(hdl) ((u32)((hdl) & 0xFF000000) >> 24)
> +#define SLIM_HDL_TO_FLOW(hdl) (((u32)(hdl) & 0xFF0000) >> 16)
> +#define SLIM_HDL_TO_PORT(hdl) ((u32)(hdl) & 0xFF)
> +
> +#define SLIM_HDL_TO_CHIDX(hdl) ((u16)(hdl) & 0xFF)
> +
> +#define SLIM_SLAVE_PORT(p, la) (((la)<<16) | (p))
> +#define SLIM_MGR_PORT(p) ((0xFF << 16) | (p))
> +#define SLIM_LA_MANAGER 0xFF
> +
> +#define SLIM_START_GRP (1 << 8)
> +#define SLIM_END_GRP (1 << 9)
> +
> +#define SLIM_MAX_INTR_COEFF_3 (SLIM_SL_PER_SUPERFRAME/3)
> +#define SLIM_MAX_INTR_COEFF_1 SLIM_SL_PER_SUPERFRAME
> +
> +static DEFINE_MUTEX(slim_lock);
> +static DEFINE_IDR(ctrl_idr);
> +static struct device_type slim_dev_type;
> +static struct device_type slim_ctrl_type;
> +
> +static int slim_compare_eaddr(struct slim_eaddr *a, struct slim_eaddr *b)
> +{
> + if (a->manf_id == b->manf_id && a->prod_code == b->prod_code &&
> + a->dev_index == b->dev_index && a->instance == b->instance)
> + return 0;
This indetation is really ugly. It makes the return 0 look like it is
part of the if expression. Same in several other places.
> + return -EIO;
> +}
> +static const struct slim_device_id *slim_match(const struct slim_device_id *id,
> + const struct slim_device *slim_dev)
> +{
> + while (id->manf_id != 0 || id->prod_code != 0) {
> + if (id->manf_id == slim_dev->e_addr.manf_id &&
> + id->prod_code == slim_dev->e_addr.prod_code &&
> + id->dev_index == slim_dev->e_addr.dev_index)
> + return id;
> + id++;
> + }
> + return NULL;
> +}
> +
> +static int slim_device_match(struct device *dev, struct device_driver *driver)
> +{
> + struct slim_device *slim_dev;
> + struct slim_driver *drv = to_slim_driver(driver);
> +
> + if (dev->type == &slim_dev_type)
> + slim_dev = to_slim_device(dev);
> + else
> + return 0;
> + if (drv->id_table)
> + return slim_match(drv->id_table, slim_dev) != NULL;
> +
> + return 0;
> +}
> +
> +static int slim_device_probe(struct device *dev)
> +{
> + struct slim_device *slim_dev;
> + struct slim_driver *driver;
> + int status = 0;
> +
> + if (dev->type == &slim_dev_type)
> + slim_dev = to_slim_device(dev);
> + else
> + return 0;
If the device is not a slim device then the probe is successful?
> + driver = to_slim_driver(dev->driver);
> + if (!driver->id_table)
> + return -ENODEV;
> + slim_dev->driver = driver;
> +
> + if (driver->probe)
> + status = driver->probe(slim_dev);
> + if (status)
> + slim_dev->driver = NULL;
> + return status;
> +}
> +
> +static int slim_device_remove(struct device *dev)
> +{
> + struct slim_device *slim_dev;
> + struct slim_driver *driver;
> + int status = 0;
> +
> + if (dev->type == &slim_dev_type)
> + slim_dev = to_slim_device(dev);
> + else
> + return 0;
> +
> + driver = to_slim_driver(dev->driver);
> + if (driver->remove)
> + status = driver->remove(slim_dev);
> +
> + if (status == 0)
> + slim_dev->driver = NULL;
> + return status;
> +}
> +
> +static void slim_device_shutdown(struct device *dev)
> +{
> + struct slim_device *slim_dev;
> + struct slim_driver *driver;
> +
> + if (dev->type == &slim_dev_type)
> + slim_dev = to_slim_device(dev);
> + else
> + return;
> +
> + driver = to_slim_driver(dev->driver);
> + if (driver->shutdown)
> + driver->shutdown(slim_dev);
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +
> +static int slim_pm_suspend(struct device *dev)
> +{
> + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
> +
> + if (pm)
> + return pm_generic_suspend(dev);
> + else
> + return 0;
> +}
> +
> +static int slim_pm_resume(struct device *dev)
> +{
> + const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
> +
> + if (pm)
> + return pm_generic_resume(dev);
> + else
> + return 0;
> +}
> +
> +#else
> +#define slim_pm_suspend NULL
> +#define slim_pm_resume NULL
> +#endif
> +
> +static const struct dev_pm_ops slimbus_pm = {
> + .suspend = slim_pm_suspend,
> + .resume = slim_pm_resume,
> + SET_RUNTIME_PM_OPS(
> + pm_generic_suspend,
> + pm_generic_resume,
> + pm_generic_runtime_idle
> + )
> +};
> +struct bus_type slimbus_type = {
> + .name = "slimbus",
> + .match = slim_device_match,
> + .probe = slim_device_probe,
> + .remove = slim_device_remove,
> + .shutdown = slim_device_shutdown,
> + .pm = &slimbus_pm,
> +};
> +EXPORT_SYMBOL_GPL(slimbus_type);
> +
> +static void __exit slimbus_exit(void)
> +{
> + bus_unregister(&slimbus_type);
> +}
> +
> +static int __init slimbus_init(void)
> +{
> + return bus_register(&slimbus_type);
> +}
> +postcore_initcall(slimbus_init);
> +module_exit(slimbus_exit);
> +/*
> + * slim_driver_register: Client driver registration with slimbus
> + * @drv:Client driver to be associated with client-device.
> + * This API will register the client driver with the slimbus
> + * It is called from the driver's module-init function.
> + */
> +int slim_driver_register(struct slim_driver *drv)
> +{
> + drv->driver.bus = &slimbus_type;
> +
> + return driver_register(&drv->driver);
> +}
> +EXPORT_SYMBOL_GPL(slim_driver_register);
> +
> +#define slim_ctrl_attr_gr NULL
> +
> +static void slim_ctrl_release(struct device *dev)
> +{
> + struct slim_controller *ctrl = to_slim_controller(dev);
> +
> + complete(&ctrl->dev_released);
> +}
> +
> +static struct device_type slim_ctrl_type = {
> + .groups = slim_ctrl_attr_gr,
> + .release = slim_ctrl_release,
> +};
> +
> +static struct slim_controller *slim_ctrl_get(struct slim_controller *ctrl)
> +{
> + if (!ctrl || !get_device(&ctrl->dev))
> + return NULL;
> +
> + return ctrl;
> +}
> +
> +static void slim_ctrl_put(struct slim_controller *ctrl)
> +{
> + if (ctrl)
> + put_device(&ctrl->dev);
> +}
> +
> +#define slim_device_attr_gr NULL
> +#define slim_device_uevent NULL
> +static void slim_dev_release(struct device *dev)
> +{
> + struct slim_device *sbdev = to_slim_device(dev);
> + slim_ctrl_put(sbdev->ctrl);
> +}
> +
> +static struct device_type slim_dev_type = {
> + .groups = slim_device_attr_gr,
> + .uevent = slim_device_uevent,
> + .release = slim_dev_release,
> +};
> +
> +/*
> + * slim_add_device: Add a new device without register board info.
> + * @ctrl: Controller to which this device is to be added to.
> + * Called when device doesn't have an explicit client-driver to be probed, or
> + * the client-driver is a module installed dynamically.
> + */
> +int slim_add_device(struct slim_controller *ctrl, struct slim_device *sbdev)
> +{
> + int ret = 0;
> + int i;
> +
> + sbdev->dev.bus = &slimbus_type;
> + sbdev->dev.parent = ctrl->dev.parent;
> + sbdev->dev.type = &slim_dev_type;
> + sbdev->ctrl = ctrl;
> + slim_ctrl_get(ctrl);
> + if (!sbdev->name) {
> + sbdev->name = kzalloc(SLIMBUS_NAME_SIZE, GFP_KERNEL);
Why allocate a fixed size? You could just have:
char name[SLIMBUS_NAME_SIZE];
in struct slim_device.
> + if (!sbdev->name)
> + return -ENOMEM;
> + snprintf(sbdev->name, SLIMBUS_NAME_SIZE, "0x%x:0x%x:0x%x:0x%x",
> + sbdev->e_addr.manf_id, sbdev->e_addr.prod_code,
> + sbdev->e_addr.dev_index,
> + sbdev->e_addr.instance);
> + }
> + pr_err("adding device:%s", sbdev->name);
Looks like leftover debug.
> + dev_set_name(&sbdev->dev, "%s", sbdev->name);
> + /* probe slave on this controller */
> + ret = device_register(&sbdev->dev);
> +
> + if (ret) {
> + slim_ctrl_put(ctrl);
> + return ret;
> + }
> +
> + mutex_init(&sbdev->sldev_reconf);
> + init_completion(&sbdev->wait_enum);
> + /* see if the device is already enumerated */
> + mutex_lock(&ctrl->m_ctrl);
> + for (i = 0; i < ctrl->num_dev; i++) {
> + if (ctrl->addrt[i].listed &&
> + (slim_compare_eaddr(&ctrl->addrt[i].eaddr,
> + &sbdev->e_addr) == 0)) {
> + sbdev->laddr = ctrl->addrt[i].laddr;
> + sbdev->enumerated = true;
> + complete(&sbdev->wait_enum);
break? Or can a slim device match multiple entries?
> + }
> + }
> + mutex_unlock(&ctrl->m_ctrl);
> +
> + INIT_LIST_HEAD(&sbdev->mark_define);
> + INIT_LIST_HEAD(&sbdev->mark_suspend);
> + INIT_LIST_HEAD(&sbdev->mark_removal);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(slim_add_device);
> +
> +struct sbi_boardinfo {
> + struct list_head list;
> + struct slim_boardinfo board_info;
> +};
> +
> +static LIST_HEAD(board_list);
> +static LIST_HEAD(slim_ctrl_list);
> +static DEFINE_MUTEX(board_lock);
> +
> +/*
> + * slim_query_device: Query and get handle to a device.
> + * @ctrl: Controller on which this device will be added/queried
> + * @e_addr: Elemental address of the device to be queried
> + * Returns pointer to a device if it has already reported. Creates a new
> + * device and returns pointer to it if the device has not yet enumerated.
> + */
> +struct slim_device *slim_query_device(struct slim_controller *ctrl,
> + struct slim_eaddr *e_addr)
> +{
> + struct slim_device *slim = NULL;
> + struct sbi_boardinfo *bi;
> + mutex_lock(&board_lock);
Blank line between variable declarations and code please.
> + list_for_each_entry(bi, &board_list, list) {
> + if (bi->board_info.bus_num != ctrl->nr)
> + continue;
> + if (slim_compare_eaddr(&bi->board_info.slim_slave->e_addr,
> + e_addr) == 0) {
> + slim = bi->board_info.slim_slave;
> + break;
> + }
> + }
> + if (!slim) {
> + slim = kzalloc(sizeof(struct slim_device), GFP_KERNEL);
> + if (!slim)
> + goto err_dev_add;
> + slim->e_addr = *e_addr;
> + bi = kzalloc(sizeof(struct sbi_boardinfo), GFP_KERNEL);
> + if (!bi) {
> + kfree(slim);
> + slim = NULL;
> + goto err_dev_add;
> + }
> + bi->board_info.bus_num = ctrl->nr;
> + bi->board_info.slim_slave = slim;
> + if (slim_add_device(ctrl, slim) != 0) {
> + kfree(bi);
> + kfree(slim);
> + slim = NULL;
> + goto err_dev_add;
> + }
> + list_add_tail(&bi->list, &board_list);
> + }
> +err_dev_add:
> + mutex_unlock(&board_lock);
I think you hold this mutex for too long. It looks like you only need to
hold it around the list_for_each, and the list_add_tail. The new device
allocation is safe to do outside of the lock.
> + return slim;
> +}
> +EXPORT_SYMBOL_GPL(slim_query_device);
> +
> +/* If controller is not present, only add to boards list */
> +static void slim_match_ctrl_to_boardinfo(struct slim_controller *ctrl,
> + struct slim_boardinfo *bi)
> +{
> + int ret;
> + int i;
> + if (ctrl->nr != bi->bus_num)
> + return;
Blank line between variable declarations and code.
> +
> + /* If the device is in logical address table then it's already added */
> + mutex_lock(&ctrl->m_ctrl);
> + for (i = 0; i < ctrl->num_dev; i++) {
> + if (ctrl->addrt[i].listed &&
> + (slim_compare_eaddr(&ctrl->addrt[i].eaddr,
> + &bi->slim_slave->e_addr) == 0)) {
> + mutex_unlock(&ctrl->m_ctrl);
> + return;
> + }
> + }
> + mutex_unlock(&ctrl->m_ctrl);
You need more blank lines to separate out logical parts of code. Here is
a good place to add one for example.
> + ret = slim_add_device(ctrl, bi->slim_slave);
> + if (ret != 0)
> + dev_err(ctrl->dev.parent, "can't create new device for %s\n",
> + bi->slim_slave->name);
Printing the error code might be helpful.
> +}
> +
> +/*
> + * slim_register_board_info: Board-initialization routine.
> + * @info: List of all devices on all controllers present on the board.
> + * @n: number of entries.
> + * API enumerates respective devices on corresponding controller.
> + * Called from board-init function.
> + */
> +int slim_register_board_info(struct slim_boardinfo const *info, unsigned n)
> +{
> + struct sbi_boardinfo *bi;
> + int i;
> +
> + bi = kzalloc(n * sizeof(*bi), GFP_KERNEL);
kcalloc.
> + if (!bi)
> + return -ENOMEM;
> +
> + for (i = 0; i < n; i++, bi++, info++) {
> + struct slim_controller *ctrl;
> +
> + memcpy(&bi->board_info, info, sizeof(*info));
> + mutex_lock(&board_lock);
> + list_add_tail(&bi->list, &board_list);
> + list_for_each_entry(ctrl, &slim_ctrl_list, list)
> + slim_match_ctrl_to_boardinfo(ctrl, &bi->board_info);
> + mutex_unlock(&board_lock);
> + }
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(slim_register_board_info);
> +
> +static int slim_register_controller(struct slim_controller *ctrl)
> +{
> + int ret = 0;
> + struct sbi_boardinfo *bi;
> +
> + /* Can't register until after driver model init */
> + if (WARN_ON(!slimbus_type.p)) {
> + ret = -EAGAIN;
> + goto out_list;
> + }
> +
> + dev_set_name(&ctrl->dev, "sb-%d", ctrl->nr);
> + ctrl->dev.bus = &slimbus_type;
> + ctrl->dev.type = &slim_ctrl_type;
> + ctrl->num_dev = 0;
> + if (!ctrl->min_cg)
> + ctrl->min_cg = SLIM_MIN_CLK_GEAR;
> + if (!ctrl->max_cg)
> + ctrl->max_cg = SLIM_MAX_CLK_GEAR;
> + mutex_init(&ctrl->m_ctrl);
> + mutex_init(&ctrl->sched.m_reconf);
> + ret = device_register(&ctrl->dev);
> + if (ret)
> + goto out_list;
> +
> + dev_dbg(&ctrl->dev, "Bus [%s] registered:dev:%x\n", ctrl->name,
> + (u32)&ctrl->dev);
> +
> + if (ctrl->nports) {
> + ctrl->ports = kzalloc(ctrl->nports * sizeof(struct slim_port),
> + GFP_KERNEL);
kcalloc.
> + if (!ctrl->ports) {
> + ret = -ENOMEM;
> + goto err_port_failed;
> + }
> + }
> + if (ctrl->nchans) {
> + ctrl->chans = kzalloc(ctrl->nchans * sizeof(struct slim_ich),
> + GFP_KERNEL);
kcalloc. Same in lots of other places.
> + if (!ctrl->chans) {
> + ret = -ENOMEM;
> + goto err_chan_failed;
> + }
> +
> + ctrl->sched.chc1 =
> + kzalloc(ctrl->nchans * sizeof(struct slim_ich *),
> + GFP_KERNEL);
> + if (!ctrl->sched.chc1) {
> + kfree(ctrl->chans);
> + ret = -ENOMEM;
> + goto err_chan_failed;
> + }
> + ctrl->sched.chc3 =
> + kzalloc(ctrl->nchans * sizeof(struct slim_ich *),
> + GFP_KERNEL);
> + if (!ctrl->sched.chc3) {
> + kfree(ctrl->sched.chc1);
> + kfree(ctrl->chans);
> + ret = -ENOMEM;
> + goto err_chan_failed;
> + }
> + }
> +#ifdef DEBUG
> + ctrl->sched.slots = kzalloc(SLIM_SL_PER_SUPERFRAME, GFP_KERNEL);
if (!ctrl->sched.slots) {
// EXPLODE!
}
> +#endif
> + init_completion(&ctrl->pause_comp);
> + /*
> + * If devices on a controller were registered before controller,
> + * this will make sure that they get probed now that controller is up
> + */
> + mutex_lock(&board_lock);
> + list_add_tail(&ctrl->list, &slim_ctrl_list);
> + list_for_each_entry(bi, &board_list, list)
> + slim_match_ctrl_to_boardinfo(ctrl, &bi->board_info);
> + mutex_unlock(&board_lock);
Have seen this code before. Maybe put it in a function?
> + return 0;
> +
> +err_chan_failed:
> + kfree(ctrl->ports);
> +err_port_failed:
> + device_unregister(&ctrl->dev);
> +out_list:
> + mutex_lock(&slim_lock);
> + idr_remove(&ctrl_idr, ctrl->nr);
> + mutex_unlock(&slim_lock);
> + return ret;
> +}
> +
> +/* slim_remove_device: Remove the effect of slim_add_device() */
> +void slim_remove_device(struct slim_device *sbdev)
> +{
> + device_unregister(&sbdev->dev);
> +}
> +EXPORT_SYMBOL_GPL(slim_remove_device);
> +
> +static void slim_ctrl_remove_device(struct slim_controller *ctrl,
> + struct slim_boardinfo *bi)
> +{
> + if (ctrl->nr == bi->bus_num)
> + slim_remove_device(bi->slim_slave);
> +}
> +
> +/*
> + * slim_del_controller: Controller tear-down.
> + * Controller added with the above API is teared down using this API.
> + */
> +int slim_del_controller(struct slim_controller *ctrl)
> +{
> + struct slim_controller *found;
> + struct sbi_boardinfo *bi;
> +
> + /* First make sure that this bus was added */
> + mutex_lock(&slim_lock);
> + found = idr_find(&ctrl_idr, ctrl->nr);
> + mutex_unlock(&slim_lock);
> + if (found != ctrl)
> + return -EINVAL;
> +
> + /* Remove all clients */
> + mutex_lock(&board_lock);
> + list_for_each_entry(bi, &board_list, list)
> + slim_ctrl_remove_device(ctrl, &bi->board_info);
> + mutex_unlock(&board_lock);
> +
> + init_completion(&ctrl->dev_released);
> + device_unregister(&ctrl->dev);
> +
> + wait_for_completion(&ctrl->dev_released);
> + list_del(&ctrl->list);
Shouldn't you be holding a lock when you do this?
> + /* free bus id */
> + mutex_lock(&slim_lock);
> + idr_remove(&ctrl_idr, ctrl->nr);
> + mutex_unlock(&slim_lock);
> +
> + kfree(ctrl->sched.chc1);
> + kfree(ctrl->sched.chc3);
> +#ifdef DEBUG
> + kfree(ctrl->sched.slots);
> +#endif
> + kfree(ctrl->chans);
> + kfree(ctrl->ports);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(slim_del_controller);
> +
> +/*
> + * slim_add_numbered_controller: Controller bring-up.
> + * @ctrl: Controller to be registered.
> + * A controller is registered with the framework using this API. ctrl->nr is the
> + * desired number with which slimbus framework registers the controller.
> + * Function will return -EBUSY if the number is in use.
> + */
> +int slim_add_numbered_controller(struct slim_controller *ctrl)
> +{
> + int id;
> + int status;
> +
> + if (ctrl->nr & ~MAX_ID_MASK)
> + return -EINVAL;
> +
> +retry:
> + if (idr_pre_get(&ctrl_idr, GFP_KERNEL) == 0)
> + return -ENOMEM;
> +
> + mutex_lock(&slim_lock);
> + status = idr_get_new_above(&ctrl_idr, ctrl, ctrl->nr, &id);
> + if (status == 0 && id != ctrl->nr) {
> + status = -EAGAIN;
> + idr_remove(&ctrl_idr, id);
> + }
> + mutex_unlock(&slim_lock);
> + if (status == -EAGAIN)
> + goto retry;
> +
> + if (status == 0)
> + status = slim_register_controller(ctrl);
> + return status;
> +}
> +EXPORT_SYMBOL_GPL(slim_add_numbered_controller);
> +
> +/*
> + * slim_msg_response: Deliver Message response received from a device to the
> + * framework.
> + * @ctrl: Controller handle
> + * @reply: Reply received from the device
> + * @len: Length of the reply
> + * @tid: Transaction ID received with which framework can associate reply.
> + * Called by controller to inform framework about the response received.
> + * This helps in making the API asynchronous, and controller-driver doesn't need
> + * to manage 1 more table other than the one managed by framework mapping TID
> + * with buffers
> + */
> +void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len)
> +{
> + int i;
> + struct slim_msg_txn *txn;
> +
> + mutex_lock(&ctrl->m_ctrl);
> + txn = ctrl->txnt[tid];
> + if (txn == NULL) {
> + dev_err(&ctrl->dev, "Got response to invalid TID:%d, len:%d",
> + tid, len);
> + mutex_unlock(&ctrl->m_ctrl);
> + return;
> + }
> + for (i = 0; i < len; i++)
> + txn->rbuf[i] = reply[i];
memcpy(txn->rbuf, reply, len);
> + if (txn->comp)
> + complete(txn->comp);
> + ctrl->txnt[tid] = NULL;
> + mutex_unlock(&ctrl->m_ctrl);
> + kfree(txn);
How does this work? You just copied data into txn->rbuf above, and then
you free txn before ever using the data? Looks broken/racy.
> +}
> +EXPORT_SYMBOL_GPL(slim_msg_response);
> +
> +static int slim_processtxn(struct slim_controller *ctrl, u8 dt, u16 mc, u16 ec,
> + u8 mt, u8 *rbuf, const u8 *wbuf, u8 len, u8 mlen,
> + struct completion *comp, u8 la, u8 *tid)
That's a lot of arguments :-/.
> +{
> + u8 i = 0;
> + int ret = 0;
> + struct slim_msg_txn *txn = kmalloc(sizeof(struct slim_msg_txn),
> + GFP_KERNEL);
> + if (!txn)
> + return -ENOMEM;
> + if (tid) {
> + mutex_lock(&ctrl->m_ctrl);
> + for (i = 0; i < ctrl->last_tid; i++) {
> + if (ctrl->txnt[i] == NULL)
> + break;
> + }
> + if (i >= ctrl->last_tid) {
> + if (ctrl->last_tid == 255) {
> + mutex_unlock(&ctrl->m_ctrl);
> + kfree(txn);
> + return -ENOMEM;
> + }
> + ctrl->txnt = krealloc(ctrl->txnt,
> + (i + 1) * sizeof(struct slim_msg_txn *),
> + GFP_KERNEL);
You need a temporary for the realloc, otherwise you leak memory in the
failure case. Avoiding realloc altogether is even better.
> + if (!ctrl->txnt) {
> + mutex_unlock(&ctrl->m_ctrl);
> + kfree(txn);
> + return -ENOMEM;
> + }
> + ctrl->last_tid++;
> + }
> + ctrl->txnt[i] = txn;
> + mutex_unlock(&ctrl->m_ctrl);
> + txn->tid = i;
> + *tid = i;
> + }
> + txn->mc = mc;
> + txn->mt = mt;
> + txn->dt = dt;
> + txn->ec = ec;
> + txn->la = la;
> + txn->rbuf = rbuf;
> + txn->wbuf = wbuf;
> + txn->rl = mlen;
> + txn->len = len;
> + txn->comp = comp;
Seems like you rework this to pass then txn structure to this function,
rather than 50 arguments that you use to initialise the txn structure.
> + ret = ctrl->xfer_msg(ctrl, txn);
> + if (!tid)
> + kfree(txn);
> + return ret;
> +}
> +
> +static int ctrl_getaddr_entry(struct slim_controller *ctrl,
> + struct slim_eaddr *eaddr, u8 *entry)
> +{
> + u8 i;
> + for (i = 0; i < ctrl->num_dev; i++) {
> + if (ctrl->addrt[i].valid &&
> + slim_compare_eaddr(&ctrl->addrt[i].eaddr, eaddr)
> + == 0) {
> + *entry = i;
> + return 0;
Horrible indentation.
> + }
> + }
> + return -ENXIO;
> +}
> +
> +/*
> + * slim_assign_laddr: Assign logical address to a device enumerated.
> + * @ctrl: Controller with which device is enumerated.
> + * @e_addr: Elemental address of the device.
> + * @laddr: Return logical address.
> + * Called by controller in response to REPORT_PRESENT. Framework will assign
> + * a logical address to this enumeration address.
> + * Function returns -EXFULL to indicate that all logical addresses are already
> + * taken.
> + */
> +int slim_assign_laddr(struct slim_controller *ctrl, struct slim_eaddr *e_addr,
> + u8 *laddr)
> +{
> + int ret;
> + u8 i = 0;
> + bool exists = false;
> + struct slim_device *slim;
Blank lines!
> + mutex_lock(&ctrl->m_ctrl);
> + /* already assigned */
> + if (ctrl_getaddr_entry(ctrl, e_addr, &i) == 0) {
> + *laddr = ctrl->addrt[i].laddr;
> + exists = true;
> + } else {
> + if (ctrl->num_dev >= 254) {
What's so magic about 254? Should probably be a MAX_ID define or something.
> + ret = -EXFULL;
> + goto ret_assigned_laddr;
> + }
> + for (i = 0; i < ctrl->num_dev; i++) {
> + if (ctrl->addrt[i].valid == false)
> + break;
> + }
> + if (i == ctrl->num_dev) {
> + ctrl->addrt = krealloc(ctrl->addrt,
> + (ctrl->num_dev + 1) *
> + sizeof(struct slim_addrt),
> + GFP_KERNEL);
Needs a temporary to avoid memory leak in failure case.
> + if (!ctrl->addrt) {
> + ret = -ENOMEM;
> + goto ret_assigned_laddr;
> + }
> + ctrl->num_dev++;
> + }
> + ctrl->addrt[i].eaddr = *e_addr;
> + ctrl->addrt[i].valid = true;
> + /* Preferred address is index into table */
> + *laddr = i;
> + }
> +
> + ret = ctrl->set_laddr(ctrl, &ctrl->addrt[i].eaddr, laddr);
> + if (ret) {
> + ctrl->addrt[i].valid = false;
> + goto ret_assigned_laddr;
> + }
> + ctrl->addrt[i].laddr = *laddr;
> +
> + dev_dbg(&ctrl->dev, "setting slimbus l-addr:%x, ea:%x,%x,%x,%x\n",
> + *laddr, e_addr->manf_id, e_addr->prod_code,
> + e_addr->dev_index, e_addr->instance);
> +ret_assigned_laddr:
> + mutex_unlock(&ctrl->m_ctrl);
> + if (exists || ret)
> + return ret;
> + /*
> + * Add this device to list of devices on this controller if it's
> + * not already present
> + */
> + slim = slim_query_device(ctrl, e_addr);
> + if (!slim)
> + ret = -ENOMEM;
> + else {
Use braces of both the if and the else if either one needs braces.
> + mutex_lock(&ctrl->m_ctrl);
> + ctrl->addrt[i].listed = true;
> + mutex_unlock(&ctrl->m_ctrl);
> + slim->laddr = *laddr;
> + slim->enumerated = true;
> + complete(&slim->wait_enum);
> + }
> +
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_assign_laddr);
> +
> +/*
> + * slim_get_logical_addr: Return the logical address of a slimbus device.
> + * @sb: client handle requesting the adddress.
> + * @e_addr: Elemental address of the device.
> + * @laddr: output buffer to store the address
> + * context: can sleep
> + * -EINVAL is returned in case of invalid parameters, and -ENXIO is returned if
> + * the device with this elemental address is not found.
> + */
> +int slim_get_logical_addr(struct slim_device *sb, struct slim_eaddr *e_addr,
> + u8 *laddr)
> +{
> + int ret = 0;
> + u8 entry = 0;
> + struct slim_controller *ctrl = sb->ctrl;
> + if (!ctrl || !laddr || !e_addr)
> + return -EINVAL;
> + mutex_lock(&ctrl->m_ctrl);
> + ret = ctrl_getaddr_entry(ctrl, e_addr, &entry);
> + if (!ret)
> + *laddr = ctrl->addrt[entry].laddr;
> + mutex_unlock(&ctrl->m_ctrl);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_get_logical_addr);
> +
> +static int slim_ele_access_sanity(struct slim_ele_access *msg, int oper,
> + u8 *rbuf, const u8 *wbuf, u8 len)
> +{
> + if (!msg || msg->num_bytes > 16 || msg->start_offset + len > 0xC00)
> + return -EINVAL;
> + switch (oper) {
> + case SLIM_MSG_MC_REQUEST_VALUE:
> + case SLIM_MSG_MC_REQUEST_INFORMATION:
> + if (rbuf == NULL)
> + return -EINVAL;
> + return 0;
> + case SLIM_MSG_MC_CHANGE_VALUE:
> + case SLIM_MSG_MC_CLEAR_INFORMATION:
> + if (wbuf == NULL)
> + return -EINVAL;
> + return 0;
> + case SLIM_MSG_MC_REQUEST_CHANGE_VALUE:
> + case SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION:
> + if (rbuf == NULL || wbuf == NULL)
> + return -EINVAL;
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static u16 slim_slicecodefromsize(u32 req)
> +{
> + static const u8 codetosize[8] = {1, 2, 3, 4, 6, 8, 12, 16};
> + if (req >= ARRAY_SIZE(codetosize))
> + return 0;
> + else
> + return codetosize[req];
> +}
> +
> +static u16 slim_slicesize(u32 code)
> +{
> + static const u8 sizetocode[16] = {0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6,
> + 6, 6, 7};
> + if (code == 0)
> + code = 1;
> + if (code > ARRAY_SIZE(sizetocode))
> + code = 16;
> + return sizetocode[code - 1];
This looks strange. Why do you index (code - 1) and have special
handling for the zero case? Can't you just shift the whole lot over by
one and index it directly?
> +}
> +
> +
> +/* Message APIs Unicast message APIs used by slimbus slave drivers */
> +
> +/*
> + * Message API access routines.
> + * @sb: client handle requesting elemental message reads, writes.
> + * @msg: Input structure for start-offset, number of bytes to read.
> + * @rbuf: data buffer to be filled with values read.
> + * @len: data buffer size
> + * @wbuf: data buffer containing value/information to be written
> + * context: can sleep
> + * Returns:
> + * -EINVAL: Invalid parameters
> + * -ETIMEDOUT: If controller could not complete the request. This may happen if
> + * the bus lines are not clocked, controller is not powered-on, slave with
> + * given address is not enumerated/responding.
> + */
> +int slim_request_val_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *buf, u8 len)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + if (!ctrl)
> + return -EINVAL;
> + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_VALUE, buf,
> + NULL, len);
> +}
> +EXPORT_SYMBOL_GPL(slim_request_val_element);
> +
> +int slim_request_inf_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *buf, u8 len)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + if (!ctrl)
> + return -EINVAL;
> + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_INFORMATION,
> + buf, NULL, len);
> +}
> +EXPORT_SYMBOL_GPL(slim_request_inf_element);
> +
> +int slim_change_val_element(struct slim_device *sb, struct slim_ele_access *msg,
> + const u8 *buf, u8 len)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + if (!ctrl)
> + return -EINVAL;
> + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_CHANGE_VALUE, NULL, buf,
> + len);
> +}
> +EXPORT_SYMBOL_GPL(slim_change_val_element);
> +
> +int slim_clear_inf_element(struct slim_device *sb, struct slim_ele_access *msg,
> + u8 *buf, u8 len)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + if (!ctrl)
> + return -EINVAL;
> + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_CLEAR_INFORMATION, NULL,
> + buf, len);
> +}
> +EXPORT_SYMBOL_GPL(slim_clear_inf_element);
> +
> +int slim_request_change_val_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *rbuf,
> + const u8 *wbuf, u8 len)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + if (!ctrl)
> + return -EINVAL;
> + return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_CHANGE_VALUE,
> + rbuf, wbuf, len);
> +}
> +EXPORT_SYMBOL_GPL(slim_request_change_val_element);
> +
> +int slim_request_clear_inf_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *rbuf,
> + const u8 *wbuf, u8 len)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + if (!ctrl)
> + return -EINVAL;
> + return slim_xfer_msg(ctrl, sb, msg,
> + SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION,
> + rbuf, wbuf, len);
> +}
> +EXPORT_SYMBOL_GPL(slim_request_clear_inf_element);
> +
> +/*
> + * Broadcast message API:
> + * call this API directly with sbdev = NULL.
> + * For broadcast reads, make sure that buffers are big-enough to incorporate
> + * replies from all logical addresses.
> + * All controllers may not support broadcast
> + */
> +int slim_xfer_msg(struct slim_controller *ctrl, struct slim_device *sbdev,
> + struct slim_ele_access *msg, u16 mc, u8 *rbuf,
> + const u8 *wbuf, u8 len)
> +{
> + DECLARE_COMPLETION_ONSTACK(complete);
> + int ret;
> + u16 sl, cur;
> + u16 ec;
> + u8 tid, mlen = 6;
> +
> + /*if (!sbdev->enumerated)
> + return -ENXIO;*/
Fix or remove.
> + ret = slim_ele_access_sanity(msg, mc, rbuf, wbuf, len);
> + if (ret)
> + goto xfer_err;
> +
> + sl = slim_slicesize(len);
> + dev_dbg(&ctrl->dev, "SB xfer msg:os:%x, len:%d, MC:%x, sl:%x\n",
> + msg->start_offset, len, mc, sl);
> +
> + cur = slim_slicecodefromsize(sl);
> + ec = ((sl | (1 << 3)) | ((msg->start_offset & 0xFFF) << 4));
Ooo, magic. What's it do? Comments/defines are helpful. I would guess
that ec means error correct, but I could be wrong.
> +
> + if (wbuf)
> + mlen += len;
> + if (rbuf) {
> + mlen++;
> + if (!msg->comp)
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR,
> + mc, ec, SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen,
> + &complete, sbdev->laddr, &tid);
> + else
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR,
> + mc, ec, SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen,
> + msg->comp, sbdev->laddr, &tid);
This looks odd. If msg->comp is not set, then you pass a completion
anyway. Why not just always the completion structure in the msg
structure and have separate msg_xfer and msg_xfer_async functions? The
former can be written in terms of the latter.
It would make the code here easier to understand and would make the api
use more clear.
> + /* sync read */
> + if (!ret && !msg->comp) {
> + ret = wait_for_completion_timeout(&complete, HZ);
> + if (!ret) {
> + struct slim_msg_txn *txn;
> + dev_err(&ctrl->dev, "slimbus Read timed out");
> + mutex_lock(&ctrl->m_ctrl);
> + txn = ctrl->txnt[tid];
> + /* Invalidate the transaction */
> + ctrl->txnt[tid] = NULL;
> + mutex_unlock(&ctrl->m_ctrl);
> + kfree(txn);
Why the temporary?
kfree(ctrl->txnt[tid]);
ctrl->txnt[tid] = NULL;
> + ret = -ETIMEDOUT;
> + } else
> + ret = 0;
Braces on the else.
Also, more blank lines throughout this function. It looks like one big
blob of text.
> + } else if (ret < 0 && !msg->comp) {
> + struct slim_msg_txn *txn;
> + dev_err(&ctrl->dev, "slimbus Read error");
> + mutex_lock(&ctrl->m_ctrl);
> + txn = ctrl->txnt[tid];
> + /* Invalidate the transaction */
> + ctrl->txnt[tid] = NULL;
> + mutex_unlock(&ctrl->m_ctrl);
> + kfree(txn);
> + }
> +
> + } else
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, ec,
> + SLIM_MSG_MT_CORE, rbuf, wbuf, len, mlen,
> + NULL, sbdev->laddr, NULL);
Braces on the else.
> +xfer_err:
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_xfer_msg);
> +
> +/*
> + * slim_alloc_mgrports: Allocate port on manager side.
> + * @sb: device/client handle.
> + * @req: Port request type.
> + * @nports: Number of ports requested
> + * @rh: output buffer to store the port handles
> + * @hsz: size of buffer storing handles
> + * context: can sleep
> + * This port will be typically used by SW. e.g. client driver wants to receive
> + * some data from audio codec HW using a data channel.
> + * Port allocated using this API will be used to receive the data.
> + * If half-duplex ports are requested, two adjacent ports are allocated for
> + * 1 half-duplex port. So the handle-buffer size should be twice the number
> + * of half-duplex ports to be allocated.
> + * -EDQUOT is returned if all ports are in use.
> + */
> +int slim_alloc_mgrports(struct slim_device *sb, enum slim_port_req req,
> + int nports, u32 *rh, int hsz)
> +{
> + int i, j;
> + int ret = -EINVAL;
> + int nphysp = nports;
> + struct slim_controller *ctrl = sb->ctrl;
> +
> + if (!rh || !ctrl)
> + return -EINVAL;
> + if (req == SLIM_REQ_HALF_DUP)
> + nphysp *= 2;
> + if (hsz/sizeof(u32) < nphysp)
> + return -EINVAL;
> + mutex_lock(&ctrl->m_ctrl);
> +
> + for (i = 0; i < ctrl->nports; i++) {
> + bool multiok = true;
> + if (ctrl->ports[i].state != SLIM_P_FREE)
> + continue;
> + /* Start half duplex channel at even port */
> + if (req == SLIM_REQ_HALF_DUP && (i % 2))
> + continue;
> + /* Allocate ports contiguously for multi-ch */
> + if (ctrl->nports < (i + nphysp)) {
> + i = ctrl->nports;
> + break;
> + }
> + if (req == SLIM_REQ_MULTI_CH) {
> + multiok = true;
> + for (j = i; j < i + nphysp; j++) {
> + if (ctrl->ports[j].state != SLIM_P_FREE) {
> + multiok = false;
> + break;
> + }
> + }
> + if (!multiok)
> + continue;
> + }
> + break;
> + }
> + if (i >= ctrl->nports)
> + ret = -EDQUOT;
> + for (j = i; j < i + nphysp; j++) {
> + ctrl->ports[j].state = SLIM_P_UNCFG;
> + ctrl->ports[j].req = req;
> + if (req == SLIM_REQ_HALF_DUP && (j % 2))
> + ctrl->ports[j].flow = SLIM_SINK;
> + else
> + ctrl->ports[j].flow = SLIM_SRC;
> + ret = ctrl->config_port(ctrl, j);
> + if (ret) {
> + for (; j >= i; j--)
> + ctrl->ports[j].state = SLIM_P_FREE;
> + goto alloc_err;
> + }
> + *rh++ = SLIM_PORT_HDL(SLIM_LA_MANAGER, 0, j);
> + }
> +alloc_err:
> + mutex_unlock(&ctrl->m_ctrl);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_alloc_mgrports);
> +
> +/* Deallocate the port(s) allocated using the API above */
> +int slim_dealloc_mgrports(struct slim_device *sb, u32 *hdl, int nports)
> +{
> + int i;
> + struct slim_controller *ctrl = sb->ctrl;
> +
> + if (!ctrl || !hdl)
> + return -EINVAL;
> +
> + mutex_lock(&ctrl->m_ctrl);
> +
> + for (i = 0; i < nports; i++) {
> + u8 pn;
> + pn = SLIM_HDL_TO_PORT(hdl[i]);
> + if (ctrl->ports[pn].state == SLIM_P_CFG) {
> + int j;
> + dev_err(&ctrl->dev, "Can't dealloc connected port:%d",
> + i);
> + for (j = i - 1; j >= 0; j--) {
> + pn = SLIM_HDL_TO_PORT(hdl[j]);
> + ctrl->ports[pn].state = SLIM_P_UNCFG;
> + }
> + mutex_unlock(&ctrl->m_ctrl);
> + return -EISCONN;
> + }
> + ctrl->ports[pn].state = SLIM_P_FREE;
> + }
> + mutex_unlock(&ctrl->m_ctrl);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(slim_dealloc_mgrports);
> +
> +/*
> + * slim_get_slaveport: Get slave port handle
> + * @la: slave device logical address.
> + * @idx: port index at slave
> + * @rh: return handle
> + * @flw: Flow type (source or destination)
> + * This API only returns a slave port's representation as expected by slimbus
> + * driver. This port is not managed by the slimbus driver. Caller is expected
> + * to have visibility of this port since it's a device-port.
> + */
> +int slim_get_slaveport(u8 la, int idx, u32 *rh, enum slim_port_flow flw)
> +{
> + if (rh == NULL)
> + return -EINVAL;
> + *rh = SLIM_PORT_HDL(la, flw, idx);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(slim_get_slaveport);
> +
> +static int connect_port_ch(struct slim_controller *ctrl, u8 ch, u32 ph,
> + enum slim_port_flow flow)
> +{
> + int ret;
> + u16 mc;
> + u8 buf[2];
> + u32 la = SLIM_HDL_TO_LA(ph);
> + u8 pn = (u8)SLIM_HDL_TO_PORT(ph);
Cast looks unneeded.
> +
> + if (flow == SLIM_SRC)
> + mc = SLIM_MSG_MC_CONNECT_SOURCE;
> + else
> + mc = SLIM_MSG_MC_CONNECT_SINK;
> + buf[0] = pn;
> + buf[1] = ctrl->chans[ch].chan;
> + if (la == SLIM_LA_MANAGER)
> + ctrl->ports[pn].flow = flow;
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, 0,
> + SLIM_MSG_MT_CORE, NULL, buf, 2, 6, NULL, la,
> + NULL);
> + if (!ret && la == SLIM_LA_MANAGER)
> + ctrl->ports[pn].state = SLIM_P_CFG;
> + return ret;
> +}
> +
> +static int disconnect_port_ch(struct slim_controller *ctrl, u32 ph)
> +{
> + int ret;
> + u16 mc;
> + u32 la = SLIM_HDL_TO_LA(ph);
> + u8 pn = (u8)SLIM_HDL_TO_PORT(ph);
> +
> + mc = SLIM_MSG_MC_DISCONNECT_PORT;
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_LOGICALADDR, mc, 0,
> + SLIM_MSG_MT_CORE, NULL, &pn, 1, 5,
> + NULL, la, NULL);
> + if (ret)
> + return ret;
> + if (la == SLIM_LA_MANAGER)
> + ctrl->ports[pn].state = SLIM_P_UNCFG;
> + return 0;
> +}
> +
> +/*
> + * slim_connect_src: Connect source port to channel.
> + * @sb: client handle
> + * @srch: source handle to be connected to this channel
> + * @chanh: Channel with which the ports need to be associated with.
> + * Per slimbus specification, a channel may have 1 source port.
> + * Channel specified in chanh needs to be allocated first.
> + * Returns -EALREADY if source is already configured for this channel.
> + * Returns -ENOTCONN if channel is not allocated
> + */
> +int slim_connect_src(struct slim_device *sb, u32 srch, u16 chanh)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + int ret;
> + u8 chan = SLIM_HDL_TO_CHIDX(chanh);
> + struct slim_ich *slc = &ctrl->chans[chan];
> + enum slim_port_flow flow = SLIM_HDL_TO_FLOW(srch);
> +
> + if (flow != SLIM_SRC)
> + return -EINVAL;
> +
> + mutex_lock(&ctrl->m_ctrl);
> +
> + if (slc->state == SLIM_CH_FREE) {
> + ret = -ENOTCONN;
> + goto connect_src_err;
> + }
> + /*
> + * Once channel is removed, its ports can be considered disconnected
> + * So its ports can be reassigned. Source port is zeroed
> + * when channel is deallocated.
> + */
> + if (slc->srch) {
> + ret = -EALREADY;
> + goto connect_src_err;
> + }
> +
> + ret = connect_port_ch(ctrl, chan, srch, SLIM_SRC);
> +
> + if (!ret)
> + slc->srch = srch;
> +
> +connect_src_err:
> + mutex_unlock(&ctrl->m_ctrl);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_connect_src);
> +
> +/*
> + * slim_connect_sink: Connect sink port(s) to channel.
> + * @sb: client handle
> + * @sinkh: sink handle(s) to be connected to this channel
> + * @nsink: number of sinks
> + * @chanh: Channel with which the ports need to be associated with.
> + * Per slimbus specification, a channel may have multiple sink-ports.
> + * Channel specified in chanh needs to be allocated first.
> + * Returns -EALREADY if sink is already configured for this channel.
> + * Returns -ENOTCONN if channel is not allocated
> + */
> +int slim_connect_sink(struct slim_device *sb, u32 *sinkh, int nsink, u16 chanh)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + int j;
> + int ret = 0;
> + u8 chan = SLIM_HDL_TO_CHIDX(chanh);
> + struct slim_ich *slc = &ctrl->chans[chan];
> +
> + if (!sinkh || !nsink)
> + return -EINVAL;
> +
> + mutex_lock(&ctrl->m_ctrl);
> +
> + /*
> + * Once channel is removed, its ports can be considered disconnected
> + * So its ports can be reassigned. Sink ports are freed when channel
> + * is deallocated.
> + */
> + if (slc->state == SLIM_CH_FREE) {
> + ret = -ENOTCONN;
> + goto connect_sink_err;
> + }
> +
> + for (j = 0; j < nsink; j++) {
> + enum slim_port_flow flow = SLIM_HDL_TO_FLOW(sinkh[j]);
> + if (flow != SLIM_SINK)
> + ret = -EINVAL;
> + else
> + ret = connect_port_ch(ctrl, chan, sinkh[j], SLIM_SINK);
> + if (ret) {
> + for (j = j - 1; j >= 0; j--)
> + disconnect_port_ch(ctrl, sinkh[j]);
> + goto connect_sink_err;
You have a few cases where you have label exits for handling the error
case, but you do half of the cleanup in here and half in the label. It
would be cleaner to put all of the cleanup in one place. Then this becomes:
if (flow != SLIM_SINK) {
ret = -EINVAL;
goto connect_sink_err;
}
ret = connect_port_ch(ctrl, chan, sinkh[j], SLIM_SINK);
if (ret)
goto connect_sink_err;
Which is a bit more readable, IMHO.
> + }
> + }
> +
> + slc->sinkh = krealloc(slc->sinkh, (sizeof(u32) * (slc->nsink + nsink)),
> + GFP_KERNEL);
Temp var for failure case.
> + if (!slc->sinkh) {
> + ret = -ENOMEM;
> + for (j = 0; j < nsink; j++)
> + disconnect_port_ch(ctrl, sinkh[j]);
> + goto connect_sink_err;
> + }
> +
> + memcpy(slc->sinkh + slc->nsink, sinkh, (sizeof(u32) * nsink));
> + slc->nsink += nsink;
> +
> +connect_sink_err:
Having error labels for the normal exit path is a little confusing.
> + mutex_unlock(&ctrl->m_ctrl);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_connect_sink);
> +
> +/*
> + * slim_disconnect_ports: Disconnect port(s) from channel
> + * @sb: client handle
> + * @ph: ports to be disconnected
> + * @nph: number of ports.
> + * Disconnects ports from a channel.
> + */
> +int slim_disconnect_ports(struct slim_device *sb, u32 *ph, int nph)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + int i;
> +
> + mutex_lock(&ctrl->m_ctrl);
> +
> + for (i = 0; i < nph; i++)
> + disconnect_port_ch(ctrl, ph[i]);
> + mutex_unlock(&ctrl->m_ctrl);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(slim_disconnect_ports);
> +
> +/*
> + * slim_port_xfer: Schedule buffer to be transferred/received using port-handle.
> + * @sb: client handle
> + * @ph: port-handle
> + * @iobuf: buffer to be transferred or populated
> + * @len: buffer size.
> + * @comp: completion signal to indicate transfer done or error.
> + * context: can sleep
> + * Returns number of bytes transferred/received if used synchronously.
> + * Will return 0 if used asynchronously.
> + * Client will call slim_port_get_xfer_status to get error and/or number of
> + * bytes transferred if used asynchronously.
> + */
> +int slim_port_xfer(struct slim_device *sb, u32 ph, u8 *iobuf, u32 len,
> + struct completion *comp)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + u8 pn = SLIM_HDL_TO_PORT(ph);
> + dev_dbg(&ctrl->dev, "port xfer: num:%d", pn);
> + return ctrl->port_xfer(ctrl, pn, iobuf, len, comp);
> +}
> +EXPORT_SYMBOL_GPL(slim_port_xfer);
> +
> +/*
> + * slim_port_get_xfer_status: Poll for port transfers, or get transfer status
> + * after completion is done.
> + * @sb: client handle
> + * @ph: port-handle
> + * @done_buf: return pointer (iobuf from slim_port_xfer) which is processed.
> + * @done_len: Number of bytes transferred.
> + * This can be called when port_xfer complition is signalled.
> + * The API will return port transfer error (underflow/overflow/disconnect)
> + * and/or done_len will reflect number of bytes transferred. Note that
> + * done_len may be valid even if port error (overflow/underflow) has happened.
> + * e.g. If the transfer was scheduled with a few bytes to be transferred and
> + * client has not supplied more data to be transferred, done_len will indicate
> + * number of bytes transferred with underflow error. To avoid frequent underflow
> + * errors, multiple transfers can be queued (e.g. ping-pong buffers) so that
> + * channel has data to be transferred even if client is not ready to transfer
> + * data all the time. done_buf will indicate address of the last buffer
> + * processed from the multiple transfers.
> + */
> +enum slim_port_err slim_port_get_xfer_status(struct slim_device *sb, u32 ph,
> + u8 **done_buf, u32 *done_len)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + u8 pn = SLIM_HDL_TO_PORT(ph);
> + u32 la = SLIM_HDL_TO_LA(ph);
> + enum slim_port_err err;
> + dev_dbg(&ctrl->dev, "get status port num:%d", pn);
> + /*
> + * Framework only has insight into ports managed by ported device
> + * used by the manager and not slave
> + */
> + if (la != SLIM_LA_MANAGER) {
> + if (done_buf)
> + *done_buf = NULL;
> + if (done_len)
> + *done_len = 0;
> + return SLIM_P_NOT_OWNED;
> + }
> + err = ctrl->port_xfer_status(ctrl, pn, done_buf, done_len);
> + if (err == SLIM_P_INPROGRESS)
> + err = ctrl->ports[pn].err;
> + return err;
> +}
> +EXPORT_SYMBOL_GPL(slim_port_get_xfer_status);
> +
> +static void slim_add_ch(struct slim_controller *ctrl, struct slim_ich *slc)
> +{
> + struct slim_ich **arr;
> + int i, j;
> + int *len;
> + int sl = slc->seglen << slc->rootexp;
> + /* Channel is already active and other end is transmitting data */
> + if (slc->state >= SLIM_CH_ACTIVE)
> + return;
> + if (slc->coeff == SLIM_COEFF_1) {
> + arr = ctrl->sched.chc1;
> + len = &ctrl->sched.num_cc1;
> + } else {
> + arr = ctrl->sched.chc3;
> + len = &ctrl->sched.num_cc3;
> + sl *= 3;
> + }
> +
> + *len += 1;
> +
> + /* Insert the channel based on rootexp and seglen */
> + for (i = 0; i < *len - 1; i++) {
> + /*
> + * Primary key: exp low to high.
> + * Secondary key: seglen: high to low
> + */
> + if ((slc->rootexp > arr[i]->rootexp) ||
> + ((slc->rootexp == arr[i]->rootexp) &&
> + (slc->seglen < arr[i]->seglen)))
> + continue;
> + else
> + break;
Reverse the test and just do the break, e.g:
if (!(big_long_winded_expression))
break;
> + }
> + for (j = *len - 1; j > i; j--)
> + arr[j] = arr[j - 1];
memmove.
> + arr[i] = slc;
> + ctrl->sched.usedslots += sl;
> +
> + return;
> +}
> +
> +static int slim_remove_ch(struct slim_controller *ctrl, struct slim_ich *slc)
> +{
> + struct slim_ich **arr;
> + int i;
> + u32 la, ph;
> + int *len;
> + if (slc->coeff == SLIM_COEFF_1) {
> + arr = ctrl->sched.chc1;
> + len = &ctrl->sched.num_cc1;
> + } else {
> + arr = ctrl->sched.chc3;
> + len = &ctrl->sched.num_cc3;
> + }
> +
> + for (i = 0; i < *len; i++) {
> + if (arr[i] == slc)
> + break;
> + }
> + if (i >= *len)
> + return -EXFULL;
> + for (; i < *len - 1; i++)
> + arr[i] = arr[i + 1];
> + *len -= 1;
> + arr[*len] = NULL;
> +
> + slc->state = SLIM_CH_ALLOCATED;
> + slc->newintr = 0;
> + slc->newoff = 0;
> + for (i = 0; i < slc->nsink; i++) {
> + ph = slc->sinkh[i];
> + la = SLIM_HDL_TO_LA(ph);
> + /*
> + * For ports managed by manager's ported device, no need to send
> + * disconnect. It is client's responsibility to call disconnect
> + * on ports owned by the slave device
> + */
> + if (la == SLIM_LA_MANAGER)
> + ctrl->ports[SLIM_HDL_TO_PORT(ph)].state = SLIM_P_UNCFG;
> + }
> +
> + ph = slc->srch;
> + la = SLIM_HDL_TO_LA(ph);
> + if (la == SLIM_LA_MANAGER)
> + ctrl->ports[SLIM_HDL_TO_PORT(ph)].state = SLIM_P_UNCFG;
> +
> + kfree(slc->sinkh);
> + slc->sinkh = NULL;
> + slc->srch = 0;
> + slc->nsink = 0;
> + return 0;
> +}
> +
> +static u32 slim_calc_prrate(struct slim_controller *ctrl, struct slim_ch *prop)
> +{
> + u32 rate = 0, rate4k = 0, rate11k = 0;
> + u32 exp = 0;
> + u32 pr = 0;
> + bool exact = true;
> + bool done = false;
> + enum slim_ch_rate ratefam;
> +
> + if (prop->prot >= SLIM_PUSH)
> + return 0;
> + if (prop->baser == SLIM_RATE_1HZ) {
> + rate = prop->ratem / 4000;
> + rate4k = rate;
> + if (rate * 4000 == prop->ratem)
> + ratefam = SLIM_RATE_4000HZ;
> + else {
> + rate = prop->ratem / 11025;
> + rate11k = rate;
> + if (rate * 11025 == prop->ratem)
> + ratefam = SLIM_RATE_11025HZ;
> + else
> + ratefam = SLIM_RATE_1HZ;
> + }
> + } else {
> + ratefam = prop->baser;
> + rate = prop->ratem;
> + }
> + if (ratefam == SLIM_RATE_1HZ) {
> + exact = false;
> + if ((rate4k + 1) * 4000 < (rate11k + 1) * 11025) {
> + rate = rate4k + 1;
> + ratefam = SLIM_RATE_4000HZ;
> + } else {
> + rate = rate11k + 1;
> + ratefam = SLIM_RATE_11025HZ;
> + }
> + }
> + /* covert rate to coeff-exp */
> + while (!done) {
> + while ((rate & 0x1) != 0x1) {
> + rate >>= 1;
> + exp++;
> + }
> + if (rate > 3) {
> + /* roundup if not exact */
> + rate++;
> + exact = false;
> + } else
> + done = true;
> + }
> + if (ratefam == SLIM_RATE_4000HZ) {
> + if (rate == 1)
> + pr = 0x10;
> + else {
> + pr = 0;
> + exp++;
> + }
> + } else {
> + pr = 8;
> + exp++;
> + }
> + if (exp <= 7) {
> + pr |= exp;
> + if (exact)
> + pr |= 0x80;
> + } else
> + pr = 0;
> + return pr;
> +}
> +
> +static int slim_nextdefine_ch(struct slim_device *sb, u8 chan)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + u32 chrate = 0;
> + u32 exp = 0;
> + u32 coeff = 0;
> + bool exact = true;
> + bool done = false;
> + int ret = 0;
> + struct slim_ich *slc = &ctrl->chans[chan];
> + struct slim_ch *prop = &slc->prop;
> +
> + slc->prrate = slim_calc_prrate(ctrl, prop);
> + dev_dbg(&ctrl->dev, "ch:%d, chan PR rate:%x\n", chan, slc->prrate);
> + if (prop->baser == SLIM_RATE_4000HZ)
> + chrate = 4000 * prop->ratem;
> + else if (prop->baser == SLIM_RATE_11025HZ)
> + chrate = 11025 * prop->ratem;
> + else
> + chrate = prop->ratem;
> + /* max allowed sample freq = 768 seg/frame */
> + if (chrate > 3600000)
> + return -EDQUOT;
> + if (prop->baser == SLIM_RATE_4000HZ &&
> + ctrl->a_framer->superfreq == 4000)
> + coeff = prop->ratem;
> + else if (prop->baser == SLIM_RATE_11025HZ &&
> + ctrl->a_framer->superfreq == 3675)
> + coeff = 3 * prop->ratem;
> + else {
> + u32 tempr = 0;
> + tempr = chrate * SLIM_CL_PER_SUPERFRAME_DIV8;
> + coeff = tempr / ctrl->a_framer->rootfreq;
> + if (coeff * ctrl->a_framer->rootfreq != tempr) {
> + coeff++;
> + exact = false;
> + }
> + }
> +
> + /* convert coeff to coeff-exponent */
> + exp = 0;
> + while (!done) {
> + while ((coeff & 0x1) != 0x1) {
> + coeff >>= 1;
> + exp++;
> + }
> + if (coeff > 3) {
> + coeff++;
> + exact = false;
> + } else
> + done = true;
> + }
> + if (prop->prot == SLIM_HARD_ISO && !exact)
> + return -EPROTONOSUPPORT;
> + else if (prop->prot == SLIM_AUTO_ISO) {
> + if (exact)
> + prop->prot = SLIM_HARD_ISO;
> + else {
> + /* Push-Pull not supported for now */
> + return -EPROTONOSUPPORT;
> + }
> + }
> + slc->rootexp = exp;
> + slc->seglen = prop->sampleszbits/SLIM_CL_PER_SL;
> + if (prop->prot != SLIM_HARD_ISO)
> + slc->seglen++;
> + if (prop->prot >= SLIM_EXT_SMPLX)
> + slc->seglen++;
> + /* convert coeff to enum */
> + if (coeff == 1) {
> + if (exp > 9)
> + ret = -EIO;
> + coeff = SLIM_COEFF_1;
> + } else {
> + if (exp > 8)
> + ret = -EIO;
> + coeff = SLIM_COEFF_3;
> + }
> + slc->coeff = coeff;
> +
> + return ret;
> +}
> +
> +/*
> + * slim_alloc_ch: Allocate a slimbus channel and return its handle.
> + * @sb: client handle.
> + * @chanh: return channel handle
> + * Slimbus channels are limited to 256 per specification.
> + * -EXFULL is returned if all channels are in use.
> + * Although slimbus specification supports 256 channels, a controller may not
> + * support that many channels.
> + */
> +int slim_alloc_ch(struct slim_device *sb, u16 *chanh)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + u16 i;
> +
> + if (!ctrl)
> + return -EINVAL;
> + mutex_lock(&ctrl->m_ctrl);
> + for (i = 0; i < ctrl->nchans; i++) {
> + if (ctrl->chans[i].state == SLIM_CH_FREE)
> + break;
> + }
> + if (i >= ctrl->nchans) {
> + mutex_unlock(&ctrl->m_ctrl);
> + return -EXFULL;
> + }
> + *chanh = i;
> + ctrl->chans[i].nextgrp = 0;
> + ctrl->chans[i].state = SLIM_CH_ALLOCATED;
> + ctrl->chans[i].chan = (u8)(ctrl->reserved + i);
> +
> + mutex_unlock(&ctrl->m_ctrl);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(slim_alloc_ch);
> +
> +/*
> + * slim_query_ch: Get reference-counted handle for a channel number. Every
> + * channel is reference counted by upto one as producer and the others as
> + * consumer)
> + * @sb: client handle
> + * @chan: slimbus channel number
> + * @chanh: return channel handle
> + * If request channel number is not in use, it is allocated, and reference
> + * count is set to one. If the channel was was already allocated, this API
> + * will return handle to that channel and reference count is incremented.
> + * -EXFULL is returned if all channels are in use
> + */
> +int slim_query_ch(struct slim_device *sb, u8 ch, u16 *chanh)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + u16 i, j;
> + int ret = 0;
> + if (!ctrl || !chanh)
> + return -EINVAL;
> + mutex_lock(&ctrl->m_ctrl);
> + /* start with modulo number */
> + i = ch % ctrl->nchans;
> +
> + for (j = 0; j < ctrl->nchans; j++) {
> + if (ctrl->chans[i].chan == ch) {
> + *chanh = i;
> + ctrl->chans[i].ref++;
> + if (ctrl->chans[i].state == SLIM_CH_FREE)
> + ctrl->chans[i].state = SLIM_CH_ALLOCATED;
> + goto query_out;
> + }
> + i = (i + 1) % ctrl->nchans;
> + }
> +
> + /* Channel not in table yet */
> + ret = -EXFULL;
> + for (j = 0; j < ctrl->nchans; j++) {
> + if (ctrl->chans[i].state == SLIM_CH_FREE) {
> + ctrl->chans[i].state =
> + SLIM_CH_ALLOCATED;
> + *chanh = i;
> + ctrl->chans[i].ref++;
> + ctrl->chans[i].chan = ch;
> + ctrl->chans[i].nextgrp = 0;
> + ret = 0;
> + break;
> + }
> + i = (i + 1) % ctrl->nchans;
> + }
> +query_out:
> + mutex_unlock(&ctrl->m_ctrl);
> + dev_dbg(&ctrl->dev, "query ch:%d,hdl:%d,ref:%d,ret:%d",
> + ch, i, ctrl->chans[i].ref, ret);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_query_ch);
> +
> +/*
> + * slim_dealloc_ch: Deallocate channel allocated using the API above
> + * -EISCONN is returned if the channel is tried to be deallocated without
> + * being removed first.
> + * -ENOTCONN is returned if deallocation is tried on a channel that's not
> + * allocated.
> + */
> +int slim_dealloc_ch(struct slim_device *sb, u16 chanh)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + u8 chan = SLIM_HDL_TO_CHIDX(chanh);
> + struct slim_ich *slc = &ctrl->chans[chan];
> + if (!ctrl)
> + return -EINVAL;
> +
> + mutex_lock(&ctrl->m_ctrl);
> + if (slc->state == SLIM_CH_FREE) {
> + mutex_unlock(&ctrl->m_ctrl);
> + return -ENOTCONN;
> + }
> + if (slc->ref > 1) {
> + slc->ref--;
> + mutex_unlock(&ctrl->m_ctrl);
> + dev_dbg(&ctrl->dev, "remove chan:%d,hdl:%d,ref:%d",
> + slc->chan, chanh, slc->ref);
> + return 0;
> + }
> + if (slc->state >= SLIM_CH_PENDING_ACTIVE) {
> + dev_err(&ctrl->dev, "Channel:%d should be removed first", chan);
> + mutex_unlock(&ctrl->m_ctrl);
> + return -EISCONN;
> + }
> + slc->ref--;
> + slc->state = SLIM_CH_FREE;
> + mutex_unlock(&ctrl->m_ctrl);
> + dev_dbg(&ctrl->dev, "remove chan:%d,hdl:%d,ref:%d",
> + slc->chan, chanh, slc->ref);
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(slim_dealloc_ch);
> +
> +/*
> + * slim_get_ch_state: Channel state.
> + * This API returns the channel's state (active, suspended, inactive etc)
> + */
> +enum slim_ch_state slim_get_ch_state(struct slim_device *sb, u16 chanh)
> +{
> + u8 chan = SLIM_HDL_TO_CHIDX(chanh);
> + struct slim_ich *slc = &sb->ctrl->chans[chan];
> + return slc->state;
> +}
> +EXPORT_SYMBOL_GPL(slim_get_ch_state);
> +
> +/*
> + * slim_define_ch: Define a channel.This API defines channel parameters for a
> + * given channel.
> + * @sb: client handle.
> + * @prop: slim_ch structure with channel parameters desired to be used.
> + * @chanh: list of channels to be defined.
> + * @nchan: number of channels in a group (1 if grp is false)
> + * @grp: Are the channels grouped
> + * @grph: return group handle if grouping of channels is desired.
> + * Channels can be grouped if multiple channels use same parameters
> + * (e.g. 5.1 audio has 6 channels with same parameters. They will all be grouped
> + * and given 1 handle for simplicity and avoid repeatedly calling the API)
> + * -EISCONN is returned if channel is already used with different parameters.
> + * -ENXIO is returned if the channel is not yet allocated.
> + */
> +int slim_define_ch(struct slim_device *sb, struct slim_ch *prop, u16 *chanh,
> + u8 nchan, bool grp, u16 *grph)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + int i, ret = 0;
> +
> + if (!ctrl || !chanh || !prop || !nchan)
> + return -EINVAL;
> + mutex_lock(&ctrl->m_ctrl);
> + for (i = 0; i < nchan; i++) {
> + u8 chan = SLIM_HDL_TO_CHIDX(chanh[i]);
> + struct slim_ich *slc = &ctrl->chans[chan];
> + dev_dbg(&ctrl->dev, "define_ch: ch:%d, state:%d", chan,
> + (int)ctrl->chans[chan].state);
> + if (slc->state < SLIM_CH_ALLOCATED) {
> + ret = -ENXIO;
> + goto err_define_ch;
> + }
> + if (slc->state >= SLIM_CH_DEFINED && slc->ref >= 2) {
> + if (prop->ratem != slc->prop.ratem ||
> + prop->sampleszbits != slc->prop.sampleszbits ||
> + prop->baser != slc->prop.baser) {
> + ret = -EISCONN;
> + goto err_define_ch;
> + }
> + } else if (slc->state > SLIM_CH_DEFINED) {
> + ret = -EISCONN;
> + goto err_define_ch;
> + } else {
> + ctrl->chans[chan].prop = *prop;
> + ret = slim_nextdefine_ch(sb, chan);
> + if (ret)
> + goto err_define_ch;
> + }
> + if (i < (nchan - 1))
> + ctrl->chans[chan].nextgrp = chanh[i + 1];
> + if (i == 0)
> + ctrl->chans[chan].nextgrp |= SLIM_START_GRP;
> + if (i == (nchan - 1))
> + ctrl->chans[chan].nextgrp |= SLIM_END_GRP;
> + }
> +
> + if (grp)
> + *grph = chanh[0];
> + for (i = 0; i < nchan; i++) {
> + u8 chan = SLIM_HDL_TO_CHIDX(chanh[i]);
> + struct slim_ich *slc = &ctrl->chans[chan];
> + if (slc->state == SLIM_CH_ALLOCATED)
> + slc->state = SLIM_CH_DEFINED;
> + }
> +err_define_ch:
> + dev_dbg(&ctrl->dev, "define_ch: ch:%d, ret:%d", *chanh, ret);
> + mutex_unlock(&ctrl->m_ctrl);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_define_ch);
> +
> +static u32 getsubfrmcoding(u32 *ctrlw, u32 *subfrml, u32 *msgsl)
> +{
> + u32 code = 0;
> + if (*ctrlw == *subfrml) {
> + *ctrlw = 8;
> + *subfrml = 8;
> + *msgsl = SLIM_SL_PER_SUPERFRAME - SLIM_FRM_SLOTS_PER_SUPERFRAME
> + - SLIM_GDE_SLOTS_PER_SUPERFRAME;
> + return 0;
> + }
> + if (*subfrml == 6) {
> + code = 0;
> + *msgsl = 256;
> + } else if (*subfrml == 8) {
> + code = 1;
> + *msgsl = 192;
> + } else if (*subfrml == 24) {
> + code = 2;
> + *msgsl = 64;
> + } else { /* 32 */
> + code = 3;
> + *msgsl = 48;
> + }
Might look nicer as a switch.
> +
> + if (*ctrlw < 8) {
> + if (*ctrlw >= 6) {
> + *ctrlw = 6;
> + code |= 0x14;
> + } else {
> + if (*ctrlw == 5)
> + *ctrlw = 4;
> + code |= (*ctrlw << 2);
> + }
> + } else {
> + code -= 2;
> + if (*ctrlw >= 24) {
> + *ctrlw = 24;
> + code |= 0x1e;
> + } else if (*ctrlw >= 16) {
> + *ctrlw = 16;
> + code |= 0x1c;
> + } else if (*ctrlw >= 12) {
> + *ctrlw = 12;
> + code |= 0x1a;
> + } else {
> + *ctrlw = 8;
> + code |= 0x18;
> + }
> + }
What does all this do? No comments, lots of magic numbers.
> +
> + *msgsl = (*msgsl * *ctrlw) - SLIM_FRM_SLOTS_PER_SUPERFRAME -
> + SLIM_GDE_SLOTS_PER_SUPERFRAME;
> + return code;
> +}
> +
> +static void shiftsegoffsets(struct slim_controller *ctrl, struct slim_ich **ach,
> + int sz, u32 shft)
allergic_to_underscores?
> +{
> + int i;
> + u32 oldoff;
> + for (i = 0; i < sz; i++) {
> + struct slim_ich *slc;
> + if (ach[i] == NULL)
> + continue;
> + slc = ach[i];
> + if (slc->state == SLIM_CH_PENDING_REMOVAL)
> + continue;
> + oldoff = slc->newoff;
> + slc->newoff += shft;
> + /* seg. offset must be <= interval */
> + if (slc->newoff >= slc->newintr)
> + slc->newoff -= slc->newintr;
> + }
> +}
> +
> +/*
> + * Find last chan in corresponding list, we will use to know when we
> +* have done scheduling all channels in that list
> +*/
> +static int slim_get_last(struct slim_ich **chc, int num_ch)
> +{
> + int last = num_ch - 1;
> + while (last >= 0) {
> + if (chc[last] != NULL &&
> + (chc[last])->state !=
> + SLIM_CH_PENDING_REMOVAL)
> + break;
> + last = last - 1;
> + }
for (last = num_ch - 1; last >= 0; last--)
?
> + return last;
> +}
> +
> +/*
> + * Find first channels with coeff 1 & 3 as starting points for
> + * scheduling
> + */
> +static int slim_get_coeff(struct slim_ich **chc, int num_ch)
> +{
> + int coeff;
> + for (coeff = 0; coeff < num_ch; coeff++) {
> + struct slim_ich *slc = chc[coeff];
> + if (slc->state == SLIM_CH_PENDING_REMOVAL)
> + continue;
> + else
> + break;
This is stupid, just do:
if (slc->state != SLIM_CH_PENDING_REMOVAL)
break;
> + }
> + return coeff;
> +}
> +static int slim_sched_coeff1_chans(struct slim_controller *ctrl, int coeff1,
> + int clkgear, int *subfrml, int *ctrlw)
> +{
> + struct slim_ich *slc1 = ctrl->sched.chc1[coeff1];
> + u32 expshft = SLIM_MAX_CLK_GEAR - clkgear;
> + int curexp, finalexp;
> + u32 curintr, curmaxsl;
> + int opensl1[2];
> + int maxctrlw1;
> + int last1 = slim_get_last(ctrl->sched.chc1, ctrl->sched.num_cc1);
> +
> + finalexp = (ctrl->sched.chc1[last1])->rootexp;
> + curexp = (int)expshft - 1;
> +
> + curintr = (SLIM_MAX_INTR_COEFF_1 * 2) >> (curexp + 1);
> + curmaxsl = curintr >> 1;
> + opensl1[0] = opensl1[1] = curmaxsl;
> +
> + while ((coeff1 < ctrl->sched.num_cc1) || (curintr > 24)) {
> + curintr >>= 1;
> + curmaxsl >>= 1;
> +
> + /* update 4K family open slot records */
> + if (opensl1[1] < opensl1[0])
> + opensl1[1] -= curmaxsl;
> + else
> + opensl1[1] = opensl1[0] - curmaxsl;
> + opensl1[0] = curmaxsl;
> + if (opensl1[1] < 0) {
> + opensl1[0] += opensl1[1];
> + opensl1[1] = 0;
> + }
> + if (opensl1[0] <= 0) {
> + dev_err(&ctrl->dev, "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + curexp++;
> + /* schedule 4k family channels */
> + while ((coeff1 < ctrl->sched.num_cc1) && (curexp ==
> + (int)(slc1->rootexp + expshft))) {
> + if (slc1->state == SLIM_CH_PENDING_REMOVAL) {
> + coeff1++;
> + slc1 = ctrl->sched.chc1[coeff1];
> + continue;
> + }
> + if (opensl1[1] >= opensl1[0] ||
> + (finalexp == (int)slc1->rootexp &&
> + curintr <= 24 &&
> + opensl1[0] == curmaxsl)) {
> + opensl1[1] -= slc1->seglen;
> + slc1->newoff = curmaxsl + opensl1[1];
> + if (opensl1[1] < 0 &&
> + opensl1[0] == curmaxsl) {
> + opensl1[0] += opensl1[1];
> + opensl1[1] = 0;
> + if (opensl1[0] < 0) {
> + dev_dbg(&ctrl->dev,
> + "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + }
> + } else {
> + if (slc1->seglen > opensl1[0]) {
> + dev_dbg(&ctrl->dev,
> + "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + slc1->newoff = opensl1[0] -
> + slc1->seglen;
> + opensl1[0] = slc1->newoff;
> + }
> + slc1->newintr = curintr;
> + coeff1++;
> + slc1 = ctrl->sched.chc1[coeff1];
> + }
> + }
> + /* Leave some slots for messaging space */
> + if (opensl1[1] == 0 && opensl1[0] == 0)
> + return -EXFULL;
> + if (opensl1[1] > opensl1[0]) {
> + int temp = opensl1[0];
> + opensl1[0] = opensl1[1];
> + opensl1[1] = temp;
> + shiftsegoffsets(ctrl, ctrl->sched.chc1,
> + ctrl->sched.num_cc1, curmaxsl);
> + }
> + /* choose subframe mode to maximize bw */
> + maxctrlw1 = opensl1[0];
> + if (opensl1[0] == curmaxsl)
> + maxctrlw1 += opensl1[1];
> + if (curintr >= 24) {
> + *subfrml = 24;
> + *ctrlw = maxctrlw1;
> + } else if (curintr == 12) {
> + if (maxctrlw1 > opensl1[1] * 4) {
> + *subfrml = 24;
> + *ctrlw = maxctrlw1;
> + } else {
> + *subfrml = 6;
> + *ctrlw = opensl1[1];
> + }
> + } else {
> + *subfrml = 6;
> + *ctrlw = maxctrlw1;
> + }
> + return 0;
Eyes glaze over :-).
> +}
> +
> +static int slim_sched_coeff1_3_chans(struct slim_controller *ctrl, int coeff1,
> + int coeff3, int clkgear, int *subfrml, int *ctrlw)
> +{
> + struct slim_ich *slc1 = NULL;
> + struct slim_ich *slc3 = ctrl->sched.chc3[coeff3];
> + u32 expshft = SLIM_MAX_CLK_GEAR - clkgear;
> + int curexp, finalexp, exp1;
> + u32 curintr, curmaxsl;
> + int opensl3[2];
> + int opensl1[6];
> + bool opensl1valid = false;
> + int maxctrlw1, maxctrlw3, i;
> + int last1 = slim_get_last(ctrl->sched.chc1, ctrl->sched.num_cc1);
> + int last3 = slim_get_last(ctrl->sched.chc3, ctrl->sched.num_cc3);
> + finalexp = (ctrl->sched.chc3[last3])->rootexp;
> + if (last1 >= 0) {
> + slc1 = ctrl->sched.chc1[coeff1];
> + exp1 = (ctrl->sched.chc1[last1])->rootexp;
> + if (exp1 > finalexp)
> + finalexp = exp1;
> + }
> + curexp = (int)expshft - 1;
> +
> + curintr = (SLIM_MAX_INTR_COEFF_3 * 2) >> (curexp + 1);
> + curmaxsl = curintr >> 1;
> + opensl3[0] = opensl3[1] = curmaxsl;
> +
> + while (coeff1 < ctrl->sched.num_cc1 ||
> + coeff3 < ctrl->sched.num_cc3 ||
> + curintr > 32) {
> + curintr >>= 1;
> + curmaxsl >>= 1;
> +
> + /* update 12k family open slot records */
> + if (opensl3[1] < opensl3[0])
> + opensl3[1] -= curmaxsl;
> + else
> + opensl3[1] = opensl3[0] - curmaxsl;
> + opensl3[0] = curmaxsl;
> + if (opensl3[1] < 0) {
> + opensl3[0] += opensl3[1];
> + opensl3[1] = 0;
> + }
> + if (opensl3[0] <= 0) {
> + dev_err(&ctrl->dev, "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + curexp++;
> +
> + /* schedule 12k family channels */
> + while (coeff3 < ctrl->sched.num_cc3 &&
> + curexp == (int)slc3->rootexp + expshft) {
> + if (slc3->state == SLIM_CH_PENDING_REMOVAL) {
> + coeff3++;
> + slc3 = ctrl->sched.chc3[coeff3];
> + continue;
> + }
> + opensl1valid = false;
> + if (opensl3[1] >= opensl3[0] ||
> + (finalexp == (int)slc3->rootexp &&
> + curintr <= 32 &&
> + opensl3[0] == curmaxsl &&
> + last1 < 0)) {
> + opensl3[1] -= slc3->seglen;
> + slc3->newoff = curmaxsl + opensl3[1];
> + if (opensl3[1] < 0 &&
> + opensl3[0] == curmaxsl) {
> + opensl3[0] += opensl3[1];
> + opensl3[1] = 0;
> + }
> + if (opensl3[0] < 0) {
> + dev_err(&ctrl->dev,
> + "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + } else {
> + if (slc3->seglen > opensl3[0]) {
> + dev_err(&ctrl->dev,
> + "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + slc3->newoff = opensl3[0] -
> + slc3->seglen;
> + opensl3[0] = slc3->newoff;
> + }
> + slc3->newintr = curintr;
> + coeff3++;
> + slc3 = ctrl->sched.chc3[coeff3];
> + }
> + /* update 4k openslot records */
> + if (opensl1valid == false) {
> + for (i = 0; i < 3; i++) {
> + opensl1[i * 2] = opensl3[0];
> + opensl1[(i * 2) + 1] = opensl3[1];
> + }
> + } else {
> + int opensl1p[6];
> + memcpy(opensl1p, opensl1, sizeof(opensl1));
> + for (i = 0; i < 3; i++) {
> + if (opensl1p[i] < opensl1p[i + 3])
> + opensl1[(i * 2) + 1] =
> + opensl1p[i];
> + else
> + opensl1[(i * 2) + 1] =
> + opensl1p[i + 3];
> + }
> + for (i = 0; i < 3; i++) {
> + opensl1[(i * 2) + 1] -= curmaxsl;
> + opensl1[i * 2] = curmaxsl;
> + if (opensl1[(i * 2) + 1] < 0) {
> + opensl1[i * 2] +=
> + opensl1[(i * 2) + 1];
> + opensl1[(i * 2) + 1] = 0;
> + }
> + if (opensl1[i * 2] < 0) {
> + dev_err(&ctrl->dev,
> + "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + }
> + }
> + /* schedule 4k family channels */
> + while (coeff1 < ctrl->sched.num_cc1 &&
> + curexp == (int)slc1->rootexp + expshft) {
> + /* searchorder effective when opensl valid */
> + static const int srcho[] = { 5, 2, 4, 1, 3, 0 };
> + int maxopensl = 0;
> + int maxi = 0;
> + if (slc1->state == SLIM_CH_PENDING_REMOVAL) {
> + coeff1++;
> + slc1 = ctrl->sched.chc1[coeff1];
> + continue;
> + }
> + opensl1valid = true;
> + for (i = 0; i < 6; i++) {
> + if (opensl1[srcho[i]] > maxopensl) {
> + maxopensl = opensl1[srcho[i]];
> + maxi = srcho[i];
> + }
> + }
> + opensl1[maxi] -= slc1->seglen;
> + slc1->newoff = (curmaxsl * maxi) +
> + opensl1[maxi];
> + if (opensl1[maxi] < 0) {
> + if (((maxi & 1) == 1) &&
> + (opensl1[maxi - 1] == curmaxsl)) {
> + opensl1[maxi - 1] +=
> + opensl1[maxi];
> + if (opensl3[0] >
> + opensl1[maxi - 1])
> + opensl3[0] =
> + opensl1[maxi - 1];
> + opensl3[1] = 0;
> + opensl1[maxi] = 0;
> + if (opensl1[maxi - 1] < 0) {
> + dev_err(&ctrl->dev,
> + "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + } else {
> + dev_err(&ctrl->dev,
> + "reconfig failed:%d\n",
> + __LINE__);
> + return -EXFULL;
> + }
> + } else {
> + if (opensl3[maxi & 1] > opensl1[maxi])
> + opensl3[maxi & 1] =
> + opensl1[maxi];
> + }
> + slc1->newintr = curintr * 3;
> + coeff1++;
> + slc1 = ctrl->sched.chc1[coeff1];
> + }
> + }
> + /* Leave some slots for messaging space */
> + if (opensl3[1] == 0 && opensl3[0] == 0)
> + return -EXFULL;
> + /* swap 1st and 2nd bucket if 2nd bucket has more open slots */
> + if (opensl3[1] > opensl3[0]) {
> + int temp = opensl3[0];
> + opensl3[0] = opensl3[1];
> + opensl3[1] = temp;
> + temp = opensl1[5];
> + opensl1[5] = opensl1[4];
> + opensl1[4] = opensl1[3];
> + opensl1[3] = opensl1[2];
> + opensl1[2] = opensl1[1];
> + opensl1[1] = opensl1[0];
> + opensl1[0] = temp;
> + shiftsegoffsets(ctrl, ctrl->sched.chc1,
> + ctrl->sched.num_cc1, curmaxsl);
> + shiftsegoffsets(ctrl, ctrl->sched.chc3,
> + ctrl->sched.num_cc3, curmaxsl);
> + }
> + /* subframe mode to maximize BW */
> + maxctrlw3 = opensl3[0];
> + maxctrlw1 = opensl1[0];
> + if (opensl3[0] == curmaxsl)
> + maxctrlw3 += opensl3[1];
> + for (i = 0; i < 5 && opensl1[i] == curmaxsl; i++)
> + maxctrlw1 += opensl1[i + 1];
> + if (curintr >= 32) {
> + *subfrml = 32;
> + *ctrlw = maxctrlw3;
> + } else if (curintr == 16) {
> + if (maxctrlw3 > (opensl3[1] * 4)) {
> + *subfrml = 32;
> + *ctrlw = maxctrlw3;
> + } else {
> + *subfrml = 8;
> + *ctrlw = opensl3[1];
> + }
> + } else {
> + if ((maxctrlw1 * 8) >= (maxctrlw3 * 24)) {
> + *subfrml = 24;
> + *ctrlw = maxctrlw1;
> + } else {
> + *subfrml = 8;
> + *ctrlw = maxctrlw3;
> + }
> + }
> + return 0;
> +}
> +
> +static int slim_sched_chans(struct slim_device *sb, u32 clkgear,
> + u32 *ctrlw, u32 *subfrml)
> +{
> + int ret;
> + struct slim_controller *ctrl = sb->ctrl;
> + int coeff1 = slim_get_coeff(ctrl->sched.chc1, ctrl->sched.num_cc1);
> + int coeff3 = slim_get_coeff(ctrl->sched.chc3, ctrl->sched.num_cc3);
> +
> + if (coeff3 == ctrl->sched.num_cc3 && coeff1 == ctrl->sched.num_cc1) {
> + *ctrlw = 8;
> + *subfrml = 8;
> + return 0;
> + } else if (coeff3 == ctrl->sched.num_cc3)
> + ret = slim_sched_coeff1_chans(ctrl, coeff1, clkgear,
> + ctrlw, subfrml);
> + else
> + ret = slim_sched_coeff1_3_chans(ctrl, coeff1, coeff3,
> + clkgear, ctrlw, subfrml);
> + return ret;
> +}
> +
> +#ifdef DEBUG
> +static int slim_verifychansched(struct slim_controller *ctrl, u32 ctrlw,
> + u32 subfrml, u32 clkgear)
> +{
> + int sl, i;
> + int cc1 = 0;
> + int cc3 = 0;
> + struct slim_ich *slc = NULL;
> + if (!ctrl->sched.slots)
> + return 0;
> + memset(ctrl->sched.slots, 0, SLIM_SL_PER_SUPERFRAME);
> + dev_dbg(&ctrl->dev, "Clock gear is:%d\n", clkgear);
> + for (sl = 0; sl < SLIM_SL_PER_SUPERFRAME; sl += subfrml) {
> + for (i = 0; i < ctrlw; i++)
> + ctrl->sched.slots[sl + i] = 33;
> + }
> + while (cc1 < ctrl->sched.num_cc1) {
> + slc = ctrl->sched.chc1[cc1];
> + if (slc == NULL) {
> + dev_err(&ctrl->dev, "SLC1 null in verify: chan%d\n",
> + cc1);
> + return -EIO;
> + }
> + dev_dbg(&ctrl->dev, "chan:%d, offset:%d, intr:%d, seglen:%d\n",
> + (slc - ctrl->chans), slc->newoff,
> + slc->newintr, slc->seglen);
> +
> + if (slc->state != SLIM_CH_PENDING_REMOVAL) {
> + for (sl = slc->newoff;
> + sl < SLIM_SL_PER_SUPERFRAME;
> + sl += slc->newintr) {
> + for (i = 0; i < slc->seglen; i++) {
> + if (ctrl->sched.slots[sl + i])
> + return -EXFULL;
> + ctrl->sched.slots[sl + i] = cc1 + 1;
> + }
> + }
> + }
> + cc1++;
> + }
> + while (cc3 < ctrl->sched.num_cc3) {
> + slc = ctrl->sched.chc3[cc3];
> + if (slc == NULL) {
> + dev_err(&ctrl->dev, "SLC3 null in verify: chan%d\n",
> + cc3);
> + return -EIO;
> + }
> + dev_dbg(&ctrl->dev, "chan:%d, offset:%d, intr:%d, seglen:%d\n",
> + (slc - ctrl->chans), slc->newoff,
> + slc->newintr, slc->seglen);
> + if (slc->state != SLIM_CH_PENDING_REMOVAL) {
> + for (sl = slc->newoff;
> + sl < SLIM_SL_PER_SUPERFRAME;
> + sl += slc->newintr) {
> + for (i = 0; i < slc->seglen; i++) {
> + if (ctrl->sched.slots[sl + i])
> + return -EXFULL;
> + ctrl->sched.slots[sl + i] = cc3 + 1;
> + }
> + }
> + }
> + cc3++;
> + }
> +
> + return 0;
> +}
> +#else
> +static int slim_verifychansched(struct slim_controller *ctrl, u32 ctrlw,
> + u32 subfrml, u32 clkgear)
> +{
> + return 0;
> +}
> +#endif
> +
> +static void slim_sort_chan_grp(struct slim_controller *ctrl,
> + struct slim_ich *slc)
> +{
> + u8 last = (u8)-1;
> + u8 second = 0;
> +
> + for (; last > 0; last--) {
> + struct slim_ich *slc1 = slc;
> + struct slim_ich *slc2;
> + u8 next = SLIM_HDL_TO_CHIDX(slc1->nextgrp);
> + slc2 = &ctrl->chans[next];
> + for (second = 1; second <= last && slc2 &&
> + (slc2->state == SLIM_CH_ACTIVE ||
> + slc2->state == SLIM_CH_PENDING_ACTIVE); second++) {
> + if (slc1->newoff > slc2->newoff) {
> + u32 temp = slc2->newoff;
> + slc2->newoff = slc1->newoff;
> + slc1->newoff = temp;
> + }
> + if (slc2->nextgrp & SLIM_END_GRP) {
> + last = second;
> + break;
> + }
> + slc1 = slc2;
> + next = SLIM_HDL_TO_CHIDX(slc1->nextgrp);
> + slc2 = &ctrl->chans[next];
> + }
> + if (slc2 == NULL)
> + last = second - 1;
> + }
> +}
> +
> +
> +static int slim_allocbw(struct slim_device *sb, int *subfrmc, int *clkgear)
> +{
> + u32 msgsl = 0;
> + u32 ctrlw = 0;
> + u32 subfrml = 0;
> + int ret = -EIO;
> + struct slim_controller *ctrl = sb->ctrl;
> + u32 usedsl = ctrl->sched.usedslots + ctrl->sched.pending_msgsl;
> + u32 availsl = SLIM_SL_PER_SUPERFRAME - SLIM_FRM_SLOTS_PER_SUPERFRAME -
> + SLIM_GDE_SLOTS_PER_SUPERFRAME;
> + *clkgear = SLIM_MAX_CLK_GEAR;
> +
> + dev_dbg(&ctrl->dev, "used sl:%u, availlable sl:%u\n", usedsl, availsl);
> + dev_dbg(&ctrl->dev, "pending:chan sl:%u, :msg sl:%u, clkgear:%u\n",
> + ctrl->sched.usedslots,
> + ctrl->sched.pending_msgsl, *clkgear);
> + /*
> + * If number of slots are 0, that means channels are inactive.
> + * It is very likely that the manager will call clock pause very soon.
> + * By making sure that bus is in MAX_GEAR, clk pause sequence will take
> + * minimum amount of time.
> + */
> + if (ctrl->sched.usedslots != 0) {
> + while ((usedsl * 2 <= availsl) && (*clkgear > ctrl->min_cg)) {
> + *clkgear -= 1;
> + usedsl *= 2;
> + }
> + }
> +
> + /*
> + * Try scheduling data channels at current clock gear, if all channels
> + * can be scheduled, or reserved BW can't be satisfied, increase clock
> + * gear and try again
> + */
> + for (; *clkgear <= ctrl->max_cg; (*clkgear)++) {
> + ret = slim_sched_chans(sb, *clkgear, &ctrlw, &subfrml);
> +
> + if (ret == 0) {
> + *subfrmc = getsubfrmcoding(&ctrlw, &subfrml, &msgsl);
> + if ((msgsl >> (ctrl->max_cg - *clkgear) <
> + ctrl->sched.pending_msgsl) &&
> + (*clkgear < ctrl->max_cg))
> + continue;
> + else
> + break;
> + }
> + }
> + if (ret == 0) {
> + int i;
> + /* Sort channel-groups */
> + for (i = 0; i < ctrl->sched.num_cc1; i++) {
> + struct slim_ich *slc = ctrl->sched.chc1[i];
> + if (slc->state == SLIM_CH_PENDING_REMOVAL)
> + continue;
> + if ((slc->nextgrp & SLIM_START_GRP) &&
> + !(slc->nextgrp & SLIM_END_GRP)) {
> + slim_sort_chan_grp(ctrl, slc);
> + }
> + }
> + for (i = 0; i < ctrl->sched.num_cc3; i++) {
> + struct slim_ich *slc = ctrl->sched.chc3[i];
> + if (slc->state == SLIM_CH_PENDING_REMOVAL)
> + continue;
> + if ((slc->nextgrp & SLIM_START_GRP) &&
> + !(slc->nextgrp & SLIM_END_GRP)) {
> + slim_sort_chan_grp(ctrl, slc);
> + }
> + }
> +
> + ret = slim_verifychansched(ctrl, ctrlw, subfrml, *clkgear);
> + }
> +
> + return ret;
> +}
> +
> +static void slim_change_existing_chans(struct slim_controller *ctrl, int coeff)
> +{
> + struct slim_ich **arr;
> + int len, i;
> + if (coeff == SLIM_COEFF_1) {
> + arr = ctrl->sched.chc1;
> + len = ctrl->sched.num_cc1;
> + } else {
> + arr = ctrl->sched.chc3;
> + len = ctrl->sched.num_cc3;
> + }
> + for (i = 0; i < len; i++) {
> + struct slim_ich *slc = arr[i];
> + if (slc->state == SLIM_CH_ACTIVE ||
> + slc->state == SLIM_CH_SUSPENDED)
> + slc->offset = slc->newoff;
> + slc->interval = slc->newintr;
> + }
> +}
> +static void slim_chan_changes(struct slim_device *sb, bool revert)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + while (!list_empty(&sb->mark_define)) {
> + struct slim_ich *slc;
> + struct slim_pending_ch *pch =
> + list_entry(sb->mark_define.next,
> + struct slim_pending_ch, pending);
> + slc = &ctrl->chans[pch->chan];
> + if (revert) {
> + if (slc->state == SLIM_CH_PENDING_ACTIVE) {
> + u32 sl = slc->seglen << slc->rootexp;
> + if (slc->coeff == SLIM_COEFF_3)
> + sl *= 3;
> + ctrl->sched.usedslots -= sl;
> + slim_remove_ch(ctrl, slc);
> + slc->state = SLIM_CH_DEFINED;
> + }
> + } else {
> + slc->state = SLIM_CH_ACTIVE;
> + slc->def++;
> + }
> + list_del_init(&pch->pending);
> + kfree(pch);
> + }
> +
> + while (!list_empty(&sb->mark_removal)) {
> + struct slim_pending_ch *pch =
> + list_entry(sb->mark_removal.next,
> + struct slim_pending_ch, pending);
> + struct slim_ich *slc = &ctrl->chans[pch->chan];
> + u32 sl = slc->seglen << slc->rootexp;
> + if (revert) {
> + if (slc->coeff == SLIM_COEFF_3)
> + sl *= 3;
> + ctrl->sched.usedslots += sl;
> + slc->def = 1;
> + slc->state = SLIM_CH_ACTIVE;
> + } else
> + slim_remove_ch(ctrl, slc);
> + list_del_init(&pch->pending);
> + kfree(pch);
> + }
> +
> + while (!list_empty(&sb->mark_suspend)) {
> + struct slim_pending_ch *pch =
> + list_entry(sb->mark_suspend.next,
> + struct slim_pending_ch, pending);
> + struct slim_ich *slc = &ctrl->chans[pch->chan];
> + if (revert)
> + slc->state = SLIM_CH_ACTIVE;
> + list_del_init(&pch->pending);
> + kfree(pch);
> + }
> + /* Change already active channel if reconfig succeeded */
> + if (!revert) {
> + slim_change_existing_chans(ctrl, SLIM_COEFF_1);
> + slim_change_existing_chans(ctrl, SLIM_COEFF_3);
> + }
> +}
> +
> +/*
> + * slim_reconfigure_now: Request reconfiguration now.
> + * @sb: client handle
> + * This API does what commit flag in other scheduling APIs do.
> + * -EXFULL is returned if there is no space in TDM to reserve the
> + * bandwidth. -EBUSY is returned if reconfiguration request is already in
> + * progress.
> + */
> +int slim_reconfigure_now(struct slim_device *sb)
> +{
> + u8 i;
> + u8 wbuf[4];
> + u32 clkgear, subframe;
> + u32 curexp;
> + int ret;
> + struct slim_controller *ctrl = sb->ctrl;
> + u32 expshft;
> + u32 segdist;
> + struct slim_pending_ch *pch;
Put same typed vars on the same line.
> +
> + mutex_lock(&ctrl->sched.m_reconf);
> + mutex_lock(&ctrl->m_ctrl);
> + ctrl->sched.pending_msgsl += sb->pending_msgsl - sb->cur_msgsl;
> + list_for_each_entry(pch, &sb->mark_define, pending) {
> + struct slim_ich *slc = &ctrl->chans[pch->chan];
> + slim_add_ch(ctrl, slc);
> + if (slc->state < SLIM_CH_ACTIVE)
> + slc->state = SLIM_CH_PENDING_ACTIVE;
> + }
> +
> + list_for_each_entry(pch, &sb->mark_removal, pending) {
> + struct slim_ich *slc = &ctrl->chans[pch->chan];
> + u32 sl = slc->seglen << slc->rootexp;
> + if (slc->coeff == SLIM_COEFF_3)
> + sl *= 3;
> + ctrl->sched.usedslots -= sl;
> + slc->state = SLIM_CH_PENDING_REMOVAL;
> + }
> + list_for_each_entry(pch, &sb->mark_suspend, pending) {
> + struct slim_ich *slc = &ctrl->chans[pch->chan];
> + slc->state = SLIM_CH_SUSPENDED;
> + }
> + mutex_unlock(&ctrl->m_ctrl);
> +
> + ret = slim_allocbw(sb, &subframe, &clkgear);
> +
> + if (!ret) {
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_BEGIN_RECONFIGURATION, 0, SLIM_MSG_MT_CORE,
> + NULL, NULL, 0, 3, NULL, 0, NULL);
> + dev_dbg(&ctrl->dev, "sending begin_reconfig:ret:%d\n", ret);
> + }
> +
> + if (!ret && subframe != ctrl->sched.subfrmcode) {
> + wbuf[0] = (u8)(subframe & 0xFF);
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_NEXT_SUBFRAME_MODE, 0, SLIM_MSG_MT_CORE,
> + NULL, (u8 *)&subframe, 1, 4, NULL, 0, NULL);
> + dev_dbg(&ctrl->dev, "sending subframe:%d,ret:%d\n",
> + (int)wbuf[0], ret);
> + }
> + if (!ret && clkgear != ctrl->clkgear) {
> + wbuf[0] = (u8)(clkgear & 0xFF);
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_NEXT_CLOCK_GEAR, 0, SLIM_MSG_MT_CORE,
> + NULL, wbuf, 1, 4, NULL, 0, NULL);
> + dev_dbg(&ctrl->dev, "sending clkgear:%d,ret:%d\n",
> + (int)wbuf[0], ret);
> + }
> + if (ret)
> + goto revert_reconfig;
> +
> + expshft = SLIM_MAX_CLK_GEAR - clkgear;
> + /* activate/remove channel */
> + list_for_each_entry(pch, &sb->mark_define, pending) {
> + struct slim_ich *slc = &ctrl->chans[pch->chan];
> + /* Define content */
> + wbuf[0] = slc->chan;
> + wbuf[1] = slc->prrate;
> + wbuf[2] = slc->prop.dataf | (slc->prop.auxf << 4);
> + wbuf[3] = slc->prop.sampleszbits / SLIM_CL_PER_SL;
> + dev_dbg(&ctrl->dev, "define content, activate:%x, %x, %x, %x\n",
> + wbuf[0], wbuf[1], wbuf[2], wbuf[3]);
> + /* Right now, channel link bit is not supported */
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_NEXT_DEFINE_CONTENT, 0,
> + SLIM_MSG_MT_CORE, NULL, (u8 *)&wbuf, 4, 7,
> + NULL, 0, NULL);
> + if (ret)
> + goto revert_reconfig;
> +
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL, 0,
> + SLIM_MSG_MT_CORE, NULL, (u8 *)&wbuf, 1, 4,
> + NULL, 0, NULL);
> + if (ret)
> + goto revert_reconfig;
> + }
> +
> + list_for_each_entry(pch, &sb->mark_removal, pending) {
> + struct slim_ich *slc = &ctrl->chans[pch->chan];
> + dev_dbg(&ctrl->dev, "remove chan:%x\n", pch->chan);
> + wbuf[0] = slc->chan;
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_NEXT_REMOVE_CHANNEL, 0,
> + SLIM_MSG_MT_CORE, NULL, wbuf, 1, 4,
> + NULL, 0, NULL);
> + if (ret)
> + goto revert_reconfig;
> + }
> + list_for_each_entry(pch, &sb->mark_suspend, pending) {
> + struct slim_ich *slc = &ctrl->chans[pch->chan];
> + dev_dbg(&ctrl->dev, "suspend chan:%x\n", pch->chan);
> + wbuf[0] = slc->chan;
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL, 0,
> + SLIM_MSG_MT_CORE, NULL, wbuf, 1, 4,
> + NULL, 0, NULL);
> + if (ret)
> + goto revert_reconfig;
> + }
> +
> + /* Define CC1 channel */
> + for (i = 0; i < ctrl->sched.num_cc1; i++) {
> + struct slim_ich *slc = ctrl->sched.chc1[i];
> + if (slc->state == SLIM_CH_PENDING_REMOVAL)
> + continue;
> + curexp = slc->rootexp + expshft;
> + segdist = (slc->newoff << curexp) & 0x1FF;
> + expshft = SLIM_MAX_CLK_GEAR - clkgear;
> + dev_dbg(&ctrl->dev, "new-intr:%d, old-intr:%d, dist:%d\n",
> + slc->newintr, slc->interval, segdist);
> + dev_dbg(&ctrl->dev, "new-off:%d, old-off:%d\n",
> + slc->newoff, slc->offset);
> +
> + if (slc->state < SLIM_CH_ACTIVE || slc->def < slc->ref ||
> + slc->newintr != slc->interval ||
> + slc->newoff != slc->offset) {
> + segdist |= 0x200;
> + segdist >>= curexp;
> + segdist |= (slc->newoff << (curexp + 1)) & 0xC00;
> + wbuf[0] = slc->chan;
> + wbuf[1] = (u8)(segdist & 0xFF);
> + wbuf[2] = (u8)((segdist & 0xF00) >> 8) |
> + (slc->prop.prot << 4);
> + wbuf[3] = slc->seglen;
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_NEXT_DEFINE_CHANNEL, 0,
> + SLIM_MSG_MT_CORE, NULL, (u8 *)wbuf, 4,
> + 7, NULL, 0, NULL);
> + if (ret)
> + goto revert_reconfig;
> + }
> + }
> +
> + /* Define CC3 channels */
> + for (i = 0; i < ctrl->sched.num_cc3; i++) {
> + struct slim_ich *slc = ctrl->sched.chc3[i];
> + if (slc->state == SLIM_CH_PENDING_REMOVAL)
> + continue;
> + curexp = slc->rootexp + expshft;
> + segdist = (slc->newoff << curexp) & 0x1FF;
> + expshft = SLIM_MAX_CLK_GEAR - clkgear;
> + dev_dbg(&ctrl->dev, "new-intr:%d, old-intr:%d, dist:%d\n",
> + slc->newintr, slc->interval, segdist);
> + dev_dbg(&ctrl->dev, "new-off:%d, old-off:%d\n",
> + slc->newoff, slc->offset);
> +
> + if (slc->state < SLIM_CH_ACTIVE || slc->def < slc->ref ||
> + slc->newintr != slc->interval ||
> + slc->newoff != slc->offset) {
> + segdist |= 0x200;
> + segdist >>= curexp;
> + segdist |= 0xC00;
> + wbuf[0] = slc->chan;
> + wbuf[1] = (u8)(segdist & 0xFF);
> + wbuf[2] = (u8)((segdist & 0xF00) >> 8) |
> + (slc->prop.prot << 4);
> + wbuf[3] = (u8)(slc->seglen);
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_NEXT_DEFINE_CHANNEL, 0,
> + SLIM_MSG_MT_CORE, NULL, (u8 *)wbuf, 4,
> + 7, NULL, 0, NULL);
> + if (ret)
> + goto revert_reconfig;
> + }
> + }
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_MC_RECONFIGURE_NOW, 0, SLIM_MSG_MT_CORE, NULL,
> + NULL, 0, 3, NULL, 0, NULL);
> + dev_dbg(&ctrl->dev, "reconfig now:ret:%d\n", ret);
> + if (!ret) {
> + mutex_lock(&ctrl->m_ctrl);
> + ctrl->sched.subfrmcode = subframe;
> + ctrl->clkgear = clkgear;
> + ctrl->sched.msgsl = ctrl->sched.pending_msgsl;
> + sb->cur_msgsl = sb->pending_msgsl;
> + slim_chan_changes(sb, false);
> + mutex_unlock(&ctrl->m_ctrl);
> + mutex_unlock(&ctrl->sched.m_reconf);
> + return 0;
> + }
> +
> +revert_reconfig:
> + mutex_lock(&ctrl->m_ctrl);
> + /* Revert channel changes */
> + slim_chan_changes(sb, true);
> + mutex_unlock(&ctrl->m_ctrl);
> + mutex_unlock(&ctrl->sched.m_reconf);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_reconfigure_now);
> +
> +static int add_pending_ch(struct list_head *listh, u8 chan)
> +{
> + struct slim_pending_ch *pch;
> + pch = kmalloc(sizeof(struct slim_pending_ch), GFP_KERNEL);
> + if (!pch)
> + return -ENOMEM;
> + pch->chan = chan;
> + list_add_tail(&pch->pending, listh);
> + return 0;
> +}
> +
> +/*
> + * slim_control_ch: Channel control API.
> + * @sb: client handle
> + * @chanh: group or channel handle to be controlled
> + * @chctrl: Control command (activate/suspend/remove)
> + * @commit: flag to indicate whether the control should take effect right-away.
> + * This API activates, removes or suspends a channel (or group of channels)
> + * chanh indicates the channel or group handle (returned by the define_ch API).
> + * Reconfiguration may be time-consuming since it can change all other active
> + * channel allocations on the bus, change in clock gear used by the slimbus,
> + * and change in the control space width used for messaging.
> + * commit makes sure that multiple channels can be activated/deactivated before
> + * reconfiguration is started.
> + * -EXFULL is returned if there is no space in TDM to reserve the bandwidth.
> + * -EISCONN/-ENOTCONN is returned if the channel is already connected or not
> + * yet defined.
> + * -EINVAL is returned if individual control of a grouped-channel is attempted.
> + */
> +int slim_control_ch(struct slim_device *sb, u16 chanh,
> + enum slim_ch_control chctrl, bool commit)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + int ret = 0;
> + /* Get rid of the group flag in MSB if any */
> + u8 chan = SLIM_HDL_TO_CHIDX(chanh);
> + struct slim_ich *slc = &ctrl->chans[chan];
> + if (!(slc->nextgrp & SLIM_START_GRP))
> + return -EINVAL;
> +
> + mutex_lock(&sb->sldev_reconf);
> + mutex_lock(&ctrl->m_ctrl);
> + do {
> + slc = &ctrl->chans[chan];
> + dev_dbg(&ctrl->dev, "chan:%d,ctrl:%d,def:%d", chan, chctrl,
> + slc->def);
> + if (slc->state < SLIM_CH_DEFINED) {
> + ret = -ENOTCONN;
> + break;
> + }
> + if (chctrl == SLIM_CH_SUSPEND) {
> + ret = add_pending_ch(&sb->mark_suspend, chan);
> + if (ret)
> + break;
> + } else if (chctrl == SLIM_CH_ACTIVATE) {
> + if (slc->state > SLIM_CH_ACTIVE) {
> + ret = -EISCONN;
> + break;
> + }
> + ret = add_pending_ch(&sb->mark_define, chan);
> + if (ret)
> + break;
> + } else {
> + if (slc->state < SLIM_CH_ACTIVE) {
> + ret = -ENOTCONN;
> + break;
> + }
> + if (slc->def > 0)
> + slc->def--;
> + if (slc->def == 0)
> + ret = add_pending_ch(&sb->mark_removal, chan);
> + if (ret)
> + break;
> + }
> +
> + if (!(slc->nextgrp & SLIM_END_GRP))
> + chan = SLIM_HDL_TO_CHIDX(slc->nextgrp);
> + } while (!(slc->nextgrp & SLIM_END_GRP));
> + mutex_unlock(&ctrl->m_ctrl);
> + if (!ret && commit == true)
> + ret = slim_reconfigure_now(sb);
> + mutex_unlock(&sb->sldev_reconf);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_control_ch);
> +
> +/*
> + * slim_reservemsg_bw: Request to reserve bandwidth for messages.
> + * @sb: client handle
> + * @bw_bps: message bandwidth in bits per second to be requested
> + * @commit: indicates whether the reconfiguration needs to be acted upon.
> + * This API call can be grouped with slim_control_ch API call with only one of
> + * the APIs specifying the commit flag to avoid reconfiguration being called too
> + * frequently. -EXFULL is returned if there is no space in TDM to reserve the
> + * bandwidth. -EBUSY is returned if reconfiguration is requested, but a request
> + * is already in progress.
> + */
> +int slim_reservemsg_bw(struct slim_device *sb, u32 bw_bps, bool commit)
> +{
> + struct slim_controller *ctrl = sb->ctrl;
> + int ret = 0;
> + int sl;
> + mutex_lock(&sb->sldev_reconf);
> + if ((bw_bps >> 3) >= ctrl->a_framer->rootfreq)
> + sl = SLIM_SL_PER_SUPERFRAME;
> + else {
> + sl = (bw_bps * (SLIM_CL_PER_SUPERFRAME_DIV8/SLIM_CL_PER_SL/2) +
> + (ctrl->a_framer->rootfreq/2 - 1)) /
> + (ctrl->a_framer->rootfreq/2);
> + }
> + dev_dbg(&ctrl->dev, "request:bw:%d, slots:%d, current:%d\n", bw_bps, sl,
> + sb->cur_msgsl);
> + sb->pending_msgsl = sl;
> + if (commit == true)
> + ret = slim_reconfigure_now(sb);
> + mutex_unlock(&sb->sldev_reconf);
> + return ret;
> +}
> +EXPORT_SYMBOL_GPL(slim_reservemsg_bw);
> +
> +/*
> + * slim_ctrl_clk_pause: Called by slimbus controller to request clock to be
> + * paused or woken up out of clock pause
> + * or woken up from clock pause
> + * @ctrl: controller requesting bus to be paused or woken up
> + * @wakeup: Wakeup this controller from clock pause.
> + * @restart: Restart time value per spec used for clock pause. This value
> + * isn't used when controller is to be woken up.
> + * This API executes clock pause reconfiguration sequence if wakeup is false.
> + * If wakeup is true, controller's wakeup is called
> + * Slimbus clock is idle and can be disabled by the controller later.
> + */
> +int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, u8 restart)
> +{
> + int ret = 0;
> + int i;
> +
> + if (wakeup == false && restart > SLIM_CLK_UNSPECIFIED)
> + return -EINVAL;
> + mutex_lock(&ctrl->m_ctrl);
> + if (wakeup) {
> + if (ctrl->clk_state == SLIM_CLK_ACTIVE) {
> + mutex_unlock(&ctrl->m_ctrl);
> + return 0;
> + }
> + wait_for_completion(&ctrl->pause_comp);
> + /*
> + * Slimbus framework will call controller wakeup
> + * Controller should make sure that it sets active framer
> + * out of clock pause by doing appropriate setting
> + */
> + if (ctrl->clk_state == SLIM_CLK_PAUSED && ctrl->wakeup)
> + ret = ctrl->wakeup(ctrl);
> + if (!ret)
> + ctrl->clk_state = SLIM_CLK_ACTIVE;
> + mutex_unlock(&ctrl->m_ctrl);
> + return ret;
> + } else {
> + switch (ctrl->clk_state) {
> + case SLIM_CLK_ENTERING_PAUSE:
> + case SLIM_CLK_PAUSE_FAILED:
> + /*
> + * If controller is already trying to enter clock pause,
> + * let it finish.
> + * In case of error, retry
> + * In both cases, previous clock pause has signalled
> + * completion.
> + */
> + wait_for_completion(&ctrl->pause_comp);
> + /* retry upon failure */
> + if (ctrl->clk_state == SLIM_CLK_PAUSE_FAILED) {
> + ctrl->clk_state = SLIM_CLK_ACTIVE;
> + break;
> + } else {
> + mutex_unlock(&ctrl->m_ctrl);
> + /*
> + * Signal completion so that wakeup can wait on
> + * it.
> + */
> + complete(&ctrl->pause_comp);
> + return 0;
> + }
> + break;
> + case SLIM_CLK_PAUSED:
> + /* already paused */
> + mutex_unlock(&ctrl->m_ctrl);
> + return 0;
> + case SLIM_CLK_ACTIVE:
> + default:
> + break;
> + }
> + }
> + /* Pending response for a message */
> + for (i = 0; i < ctrl->last_tid; i++) {
> + if (ctrl->txnt[i]) {
> + ret = -EBUSY;
> + mutex_unlock(&ctrl->m_ctrl);
> + return -EBUSY;
> + }
> + }
> + ctrl->clk_state = SLIM_CLK_ENTERING_PAUSE;
> + mutex_unlock(&ctrl->m_ctrl);
> +
> + mutex_lock(&ctrl->sched.m_reconf);
> + /* Data channels active */
> + if (ctrl->sched.usedslots) {
> + ret = -EBUSY;
> + goto clk_pause_ret;
> + }
> +
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_BEGIN_RECONFIGURATION,
> + 0, SLIM_MSG_MT_CORE, NULL, NULL, 0, 3, NULL, 0, NULL);
> + if (ret)
> + goto clk_pause_ret;
> +
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_NEXT_PAUSE_CLOCK, 0,
> + SLIM_MSG_MT_CORE, NULL, &restart, 1, 4, NULL, 0, NULL);
> + if (ret)
> + goto clk_pause_ret;
> +
> + ret = slim_processtxn(ctrl, SLIM_MSG_DEST_BROADCAST,
> + SLIM_MSG_CLK_PAUSE_SEQ_FLG | SLIM_MSG_MC_RECONFIGURE_NOW, 0,
> + SLIM_MSG_MT_CORE, NULL, NULL, 0, 3, NULL, 0, NULL);
> + if (ret)
> + goto clk_pause_ret;
> +
> +clk_pause_ret:
> + if (ret)
> + ctrl->clk_state = SLIM_CLK_PAUSE_FAILED;
> + else
> + ctrl->clk_state = SLIM_CLK_PAUSED;
> + complete(&ctrl->pause_comp);
> + mutex_unlock(&ctrl->sched.m_reconf);
> + return ret;
> +}
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Slimbus module");
> +MODULE_ALIAS("platform:slimbus");
> diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
> index 501da4c..b631dbb 100644
> --- a/include/linux/mod_devicetable.h
> +++ b/include/linux/mod_devicetable.h
> @@ -445,6 +445,13 @@ struct spi_device_id {
> __attribute__((aligned(sizeof(kernel_ulong_t))));
> };
>
> +struct slim_device_id {
> + __u16 manf_id, prod_code;
> + __u8 dev_index, instance;
> + kernel_ulong_t driver_data /* Data private to the driver */
> + __attribute__((aligned(sizeof(kernel_ulong_t))));
> +};
> +
> /* dmi */
> enum dmi_field {
> DMI_NONE,
> diff --git a/include/linux/of_slimbus.h b/include/linux/of_slimbus.h
> new file mode 100644
> index 0000000..8e1dc65
> --- /dev/null
> +++ b/include/linux/of_slimbus.h
> @@ -0,0 +1,34 @@
> +/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/slimbus/slimbus.h>
> +#include <linux/of_irq.h>
> +
> +#ifdef CONFIG_OF_SLIMBUS
> +/*
> + * of_slim_register_devices() - Register devices in the SLIMbus Device Tree
> + * @ctrl: slim_controller which devices should be registered to.
> + *
> + * This routine scans the SLIMbus Device Tree, allocating resources and
> + * creating slim_devices according to the SLIMbus Device Tree
> + * hierarchy. Details of this hierarchy can be found in
> + * Documentation/devicetree/bindings/slimbus. This routine is normally
> + * called from the probe routine of the driver registering as a
> + * slim_controller.
> + */
> +extern int of_register_slim_devices(struct slim_controller *ctrl);
> +#else
> +static int of_register_slim_devices(struct slim_controller *ctrl)
> +{
> + return 0;
> +}
> +#endif /* CONFIG_OF_SLIMBUS */
> diff --git a/include/linux/slimbus/slimbus.h b/include/linux/slimbus/slimbus.h
> new file mode 100644
> index 0000000..ef3f614
> --- /dev/null
> +++ b/include/linux/slimbus/slimbus.h
> @@ -0,0 +1,1072 @@
> +/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef _LINUX_SLIMBUS_H
> +#define _LINUX_SLIMBUS_H
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/mutex.h>
> +#include <linux/mod_devicetable.h>
> +
> +#define SLIMBUS_NAME_SIZE 32
> +/* Interfaces between SLIMbus manager drivers and SLIMbus infrastructure. */
> +
> +extern struct bus_type slimbus_type;
> +
> +/* Standard values per SLIMbus spec needed by controllers and devices */
> +#define SLIM_CL_PER_SUPERFRAME 6144
> +#define SLIM_CL_PER_SUPERFRAME_DIV8 (SLIM_CL_PER_SUPERFRAME >> 3)
> +#define SLIM_MAX_CLK_GEAR 10
> +#define SLIM_MIN_CLK_GEAR 1
> +#define SLIM_CL_PER_SL 4
> +#define SLIM_SL_PER_SUPERFRAME (SLIM_CL_PER_SUPERFRAME >> 2)
> +#define SLIM_FRM_SLOTS_PER_SUPERFRAME 16
> +#define SLIM_GDE_SLOTS_PER_SUPERFRAME 2
> +
> +/*
> + * SLIMbus message types. Related to interpretation of message code.
> + * Values are defined in Table 32 (slimbus spec 1.01.01)
> + */
> +#define SLIM_MSG_MT_CORE 0x0
> +#define SLIM_MSG_MT_DEST_REFERRED_CLASS 0x1
> +#define SLIM_MSG_MT_DEST_REFERRED_USER 0x2
> +#define SLIM_MSG_MT_SRC_REFERRED_CLASS 0x5
> +#define SLIM_MSG_MT_SRC_REFERRED_USER 0x6
> +
> +/*
> + * SLIMbus core type Message Codes.
> + * Values are defined in Table 65 (slimbus spec 1.01.01)
> + */
> +/* Device management messages */
> +#define SLIM_MSG_MC_REPORT_PRESENT 0x1
> +#define SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS 0x2
> +#define SLIM_MSG_MC_RESET_DEVICE 0x4
> +#define SLIM_MSG_MC_CHANGE_LOGICAL_ADDRESS 0x8
> +#define SLIM_MSG_MC_CHANGE_ARBITRATION_PRIORITY 0x9
> +#define SLIM_MSG_MC_REQUEST_SELF_ANNOUNCEMENT 0xC
> +#define SLIM_MSG_MC_REPORT_ABSENT 0xF
> +
> +/* Data channel management messages */
> +#define SLIM_MSG_MC_CONNECT_SOURCE 0x10
> +#define SLIM_MSG_MC_CONNECT_SINK 0x11
> +#define SLIM_MSG_MC_DISCONNECT_PORT 0x14
> +#define SLIM_MSG_MC_CHANGE_CONTENT 0x18
> +
> +/* Information management messages */
> +#define SLIM_MSG_MC_REQUEST_INFORMATION 0x20
> +#define SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION 0x21
> +#define SLIM_MSG_MC_REPLY_INFORMATION 0x24
> +#define SLIM_MSG_MC_CLEAR_INFORMATION 0x28
> +#define SLIM_MSG_MC_REPORT_INFORMATION 0x29
> +
> +/* Reconfiguration messages */
> +#define SLIM_MSG_MC_BEGIN_RECONFIGURATION 0x40
> +#define SLIM_MSG_MC_NEXT_ACTIVE_FRAMER 0x44
> +#define SLIM_MSG_MC_NEXT_SUBFRAME_MODE 0x45
> +#define SLIM_MSG_MC_NEXT_CLOCK_GEAR 0x46
> +#define SLIM_MSG_MC_NEXT_ROOT_FREQUENCY 0x47
> +#define SLIM_MSG_MC_NEXT_PAUSE_CLOCK 0x4A
> +#define SLIM_MSG_MC_NEXT_RESET_BUS 0x4B
> +#define SLIM_MSG_MC_NEXT_SHUTDOWN_BUS 0x4C
> +#define SLIM_MSG_MC_NEXT_DEFINE_CHANNEL 0x50
> +#define SLIM_MSG_MC_NEXT_DEFINE_CONTENT 0x51
> +#define SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL 0x54
> +#define SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL 0x55
> +#define SLIM_MSG_MC_NEXT_REMOVE_CHANNEL 0x58
> +#define SLIM_MSG_MC_RECONFIGURE_NOW 0x5F
> +
> +/*
> + * Clock pause flag to indicate that the reconfig message
> + * corresponds to clock pause sequence
> + */
> +#define SLIM_MSG_CLK_PAUSE_SEQ_FLG (1U << 8)
> +
> +/* Value management messages */
> +#define SLIM_MSG_MC_REQUEST_VALUE 0x60
> +#define SLIM_MSG_MC_REQUEST_CHANGE_VALUE 0x61
> +#define SLIM_MSG_MC_REPLY_VALUE 0x64
> +#define SLIM_MSG_MC_CHANGE_VALUE 0x68
> +
> +/* Clock pause values defined in Table 66 (slimbus spec 1.01.01) */
> +#define SLIM_CLK_FAST 0
> +#define SLIM_CLK_CONST_PHASE 1
> +#define SLIM_CLK_UNSPECIFIED 2
> +
> +struct slim_controller;
> +struct slim_device;
> +
> +/*
> + * struct slim_eaddr: Elemental address for a slimbus device
> + * @manf_id: Manufacturer Id for the device
> + * @prod_code: Product code
> + * @dev_index: Device index
> + * @instance: Instance value
> + */
> +struct slim_eaddr {
> + u16 manf_id;
> + u16 prod_code;
> + u8 dev_index;
> + u8 instance;
> +};
> +
> +/* Destination type Values defined in Table 33 (slimbus spec 1.01.01) */
> +#define SLIM_MSG_DEST_LOGICALADDR 0
> +#define SLIM_MSG_DEST_ENUMADDR 1
> +#define SLIM_MSG_DEST_BROADCAST 3
> +
> +/*
> + * @start_offset: Specifies starting offset in information/value element map
> + * @num_bytes: Can be 1, 2, 3, 4, 6, 8, 12, 16 per spec. This ensures that the
> + * message will fit in the 40-byte message limit and the slicesize can be
> + * compatible with values in table 21 (slimbus spec 1.01.01)
> + * @comp: Completion to indicate end of message-transfer. Used if client wishes
> + * to use the API asynchronously.
> + */
> +struct slim_ele_access {
> + u16 start_offset;
> + u8 num_bytes;
> + struct completion *comp;
> +};
> +
> +/*
> + * struct slim_framer - Represents Slimbus framer.
> + * Every controller may have multiple framers.
> + * Manager is responsible for framer hand-over.
> + * @e_addr: Elemental address of the framer.
> + * @rootfreq: Root Frequency at which the framer can run. This is maximum
> + * frequency (clock gear 10 per slimbus spec) at which the bus can operate.
> + * @superfreq: Superframes per root frequency. Every frame is 6144 cells (bits)
> + * per slimbus specification.
> + */
> +struct slim_framer {
> + struct slim_eaddr e_addr;
> + int rootfreq;
> + int superfreq;
> +};
> +#define to_slim_framer(d) container_of(d, struct slim_framer, dev);
> +
> +/*
> + * struct slim_addrt: slimbus address used internally by the slimbus framework.
> + * @valid: If the device is still there or if the address can be reused.
> + * @listed: Each device is added to board-list either when it's enumerated, or
> + * when it's listed as part of boardinfo (whichever happens first).
> + * This field is set to true if the device is listed first through
> + * enumeration so that register_board_info doesn't add this again if it's
> + * called after the device was enumerated.
> + * @eaddr: Elemental address
> + * @laddr: It is possible that controller will set a predefined logical address
> + * rather than the one assigned by framework. (i.e. logical address may
> + * not be same as index into this table). This entry will store the
> + * logical address value for this enumeration address.
> + */
> +struct slim_addrt {
> + bool valid;
> + bool listed;
> + struct slim_eaddr eaddr;
> + u8 laddr;
> +};
> +
> +/*
> + * struct slim_msg_txn: Message to be sent by the controller.
> + * Linux framework uses this structure with drivers implementing controller.
> + * This structure has packet header, payload and buffer to be filled (if any)
> + * For the header information, refer to Table 34-36.
> + * @rl: Header field. remaining length.
> + * @mt: Header field. Message type.
> + * @mc: Header field. LSB is message code for type mt. Framework will set MSB to
> + * SLIM_MSG_CLK_PAUSE_SEQ_FLG in case "mc" in the reconfiguration sequence
> + * is for pausing the clock.
> + * @dt: Header field. Destination type.
> + * @ec: Element size. Used for elemental access APIs.
> + * @len: Length of payload. (excludes ec)
> + * @tid: Transaction ID. Used for messages expecting response.
> + * (e.g. relevant for mc = SLIM_MSG_MC_REQUEST_INFORMATION)
> + * @la: Logical address of the device this message is going to.
> + * (Not used when destination type is broadcast.)
> + * @rbuf: Buffer to be populated by controller when response is received.
> + * @wbuf: Payload of the message. (e.g. channel number for DATA channel APIs)
> + * @comp: Completion structure. Used by controller to notify response.
> + * (Field is relevant when tid is used)
> + */
> +struct slim_msg_txn {
> + u8 rl;
> + u8 mt;
> + u16 mc;
> + u8 dt;
> + u16 ec;
> + u8 len;
> + u8 tid;
> + u8 la;
> + u8 *rbuf;
> + const u8 *wbuf;
> + struct completion *comp;
> +};
> +
> +/* Internal port state used by slimbus framework to manage data-ports */
> +enum slim_port_state {
> + SLIM_P_FREE,
> + SLIM_P_UNCFG,
> + SLIM_P_CFG,
> +};
> +
> +/*
> + * enum slim_port_req: Request port type by user through APIs to manage ports
> + * User can request default, half-duplex or port to be used in multi-channel
> + * configuration. Default indicates a simplex port.
> + */
> +enum slim_port_req {
> + SLIM_REQ_DEFAULT,
> + SLIM_REQ_HALF_DUP,
> + SLIM_REQ_MULTI_CH,
> +};
> +
> +/*
> + * enum slim_port_cfg: Port configuration parameters requested.
> + * User can request no configuration, packed data, or MSB aligned data port
> + */
> +enum slim_port_cfg {
> + SLIM_CFG_NONE,
> + SLIM_CFG_PACKED,
> + SLIM_CFG_ALIGN_MSB,
> +};
> +
> +/* enum slim_port_flow: Port flow type (inbound/outbound). */
> +enum slim_port_flow {
> + SLIM_SRC,
> + SLIM_SINK,
> +};
> +
> +/* enum slim_port_err: Port errors */
> +enum slim_port_err {
> + SLIM_P_INPROGRESS,
> + SLIM_P_OVERFLOW,
> + SLIM_P_UNDERFLOW,
> + SLIM_P_DISCONNECT,
> + SLIM_P_NOT_OWNED,
> +};
> +
> +/*
> + * struct slim_port: Internal structure used by framework to manage ports
> + * @err: Port error if any for this port. Refer to enum above.
> + * @state: Port state. Refer to enum above.
> + * @req: Port request for this port.
> + * @cfg: Port configuration for this port.
> + * @flow: Flow type of this port.
> + * @ch: Channel association of this port.
> + * @xcomp: Completion to indicate error, data transfer done event.
> + * @ctrl: Controller to which this port belongs to. This is useful to associate
> + * port with the SW since port hardware interrupts may only contain port
> + * information.
> + */
> +struct slim_port {
> + enum slim_port_err err;
> + enum slim_port_state state;
> + enum slim_port_req req;
> + enum slim_port_cfg cfg;
> + enum slim_port_flow flow;
> + struct slim_ch *ch;
> + struct completion *xcomp;
> + struct slim_controller *ctrl;
> +};
> +
> +/*
> + * enum slim_ch_state: Channel state of a channel.
> + * Channel transition happens from free-to-allocated-to-defined-to-pending-
> + * active-to-active.
> + * Once active, channel can be removed or suspended. Suspended channels are
> + * still scheduled, but data transfer doesn't happen.
> + * Removed channels are not deallocated until dealloc_ch API is used.
> + * Deallocation reset channel state back to free.
> + * Removed channels can be defined with different parameters.
> + */
> +enum slim_ch_state {
> + SLIM_CH_FREE,
> + SLIM_CH_ALLOCATED,
> + SLIM_CH_DEFINED,
> + SLIM_CH_PENDING_ACTIVE,
> + SLIM_CH_ACTIVE,
> + SLIM_CH_SUSPENDED,
> + SLIM_CH_PENDING_REMOVAL,
> +};
> +
> +/*
> + * enum slim_ch_proto: Channel protocol used by the channel.
> + * Hard Isochronous channel is not scheduled if current frequency doesn't allow
> + * the channel to be run without flow-control.
> + * Auto isochronous channel will be scheduled as hard-isochronous or push-pull
> + * depending on current bus frequency.
> + * Currently, Push-pull or async or extended channels are not supported.
> + * For more details, refer to slimbus spec
> + */
> +enum slim_ch_proto {
> + SLIM_HARD_ISO,
> + SLIM_AUTO_ISO,
> + SLIM_PUSH,
> + SLIM_PULL,
> + SLIM_ASYNC_SMPLX,
> + SLIM_ASYNC_HALF_DUP,
> + SLIM_EXT_SMPLX,
> + SLIM_EXT_HALF_DUP,
> +};
> +
> +/*
> + * enum slim_ch_rate: Most commonly used frequency rate families.
> + * Use 1HZ for push-pull transport.
> + * 4KHz and 11.025KHz are most commonly used in audio applications.
> + * Typically, slimbus runs at frequencies to support channels running at 4KHz
> + * and/or 11.025KHz isochronously.
> + */
> +enum slim_ch_rate {
> + SLIM_RATE_1HZ,
> + SLIM_RATE_4000HZ,
> + SLIM_RATE_11025HZ,
> +};
> +
> +/*
> + * enum slim_ch_coeff: Coefficient of a channel used internally by framework.
> + * Coefficient is applicable to channels running isochronously.
> + * Coefficient is calculated based on channel rate multiplier.
> + * (If rate multiplier is power of 2, it's coeff.1 channel. Otherwise it's
> + * coeff.3 channel.
> + */
> +enum slim_ch_coeff {
> + SLIM_COEFF_1,
> + SLIM_COEFF_3,
> +};
> +
> +/*
> + * enum slim_ch_control: Channel control.
> + * Activate will schedule channel and/or group of channels in the TDM frame.
> + * Suspend will keep the schedule but data-transfer won't happen.
> + * Remove will remove the channel/group from the TDM frame.
> + */
> +enum slim_ch_control {
> + SLIM_CH_ACTIVATE,
> + SLIM_CH_SUSPEND,
> + SLIM_CH_REMOVE,
> +};
> +
> +/* enum slim_ch_dataf: Data format per table 60 from slimbus spec 1.01.01 */
> +enum slim_ch_dataf {
> + SLIM_CH_DATAF_NOT_DEFINED = 0,
> + SLIM_CH_DATAF_LPCM_AUDIO = 1,
> + SLIM_CH_DATAF_IEC61937_COMP_AUDIO = 2,
> + SLIM_CH_DATAF_PACKED_PDM_AUDIO = 3,
> +};
> +
> +/* enum slim_ch_auxf: Auxiliary field format per table 59 from slimbus spec */
> +enum slim_ch_auxf {
> + SLIM_CH_AUXF_NOT_APPLICABLE = 0,
> + SLIM_CH_AUXF_ZCUV_TUNNEL_IEC60958 = 1,
> + SLIM_CH_USER_DEFINED = 0xF,
> +};
> +
> +/*
> + * struct slim_ch: Channel structure used externally by users of channel APIs.
> + * @prot: Desired slimbus protocol.
> + * @baser: Desired base rate. (Typical isochronous rates are: 4KHz, or 11.025KHz
> + * @dataf: Data format.
> + * @auxf: Auxiliary format.
> + * @ratem: Channel rate multiplier. (e.g. 48KHz channel will have 4KHz base rate
> + * and 12 as rate multiplier.
> + * @sampleszbits: Sample size in bits.
> + */
> +struct slim_ch {
> + enum slim_ch_proto prot;
> + enum slim_ch_rate baser;
> + enum slim_ch_dataf dataf;
> + enum slim_ch_auxf auxf;
> + u32 ratem;
> + u32 sampleszbits;
> +};
> +
> +/*
> + * struct slim_ich: Internal channel structure used by slimbus framework.
> + * @prop: structure passed by the client.
> + * @coeff: Coefficient of this channel.
> + * @state: Current state of the channel.
> + * @nextgrp: If this channel is part of group, next channel in this group.
> + * @prrate: Presence rate of this channel (per table 62 of the spec)
> + * @offset: Offset of this channel in the superframe.
> + * @newoff: Used during scheduling to hold temporary new offset until the offset
> + * is accepted/rejected by slimbus reconfiguration.
> + * @interval: Interval of this channel per superframe.
> + * @newintr: Used during scheduling to new interval temporarily.
> + * @seglen: Segment length of this channel.
> + * @rootexp: root exponent of this channel. Rate can be found using rootexp and
> + * coefficient. Used during scheduling.
> + * @srch: Source port used by this channel.
> + * @sinkh: Sink ports used by this channel.
> + * @nsink: number of sink ports used by this channel.
> + * @chan: Channel number sent on hardware lines for this channel. May not be
> + * equal to array-index into chans if client requested to use number beyond
> + * channel-array for the controller.
> + * @ref: Reference number to keep track of how many clients (upto 2) are using
> + * this channel.
> + * @def: Used to keep track of how many times the channel definition is sent
> + * to hardware and this will decide if channel-remove can be sent for the
> + * channel. Channel definition may be sent upto twice (once per producer
> + * and once per consumer). Channel removal should be sent only once to
> + * avoid clients getting underflow/overflow errors.
> + */
> +struct slim_ich {
> + struct slim_ch prop;
> + enum slim_ch_coeff coeff;
> + enum slim_ch_state state;
> + u16 nextgrp;
> + u32 prrate;
> + u32 offset;
> + u32 newoff;
> + u32 interval;
> + u32 newintr;
> + u32 seglen;
> + u8 rootexp;
> + u32 srch;
> + u32 *sinkh;
> + int nsink;
> + u8 chan;
> + int ref;
> + int def;
> +};
> +
> +/*
> + * struct slim_sched: Framework uses this structure internally for scheduling.
> + * @chc3: Array of all active coeffient 3 channels.
> + * @num_cc3: Number of active coeffient 3 channels.
> + * @chc1: Array of all active coeffient 1 channels.
> + * @num_cc1: Number of active coeffient 1 channels.
> + * @subfrmcode: Current subframe-code used by TDM. This is decided based on
> + * requested message bandwidth and current channels scheduled.
> + * @usedslots: Slots used by all active channels.
> + * @msgsl: Slots used by message-bandwidth.
> + * @pending_msgsl: Used to store pending request of message bandwidth (in slots)
> + * until the scheduling is accepted by reconfiguration.
> + * @m_reconf: This mutex is held until current reconfiguration (data channel
> + * scheduling, message bandwidth reservation) is done. Message APIs can
> + * use the bus concurrently when this mutex is held since elemental access
> + * messages can be sent on the bus when reconfiguration is in progress.
> + * @slots: Used for debugging purposes to debug/verify current schedule in TDM.
> + */
> +struct slim_sched {
> + struct slim_ich **chc3;
> + int num_cc3;
> + struct slim_ich **chc1;
> + int num_cc1;
> + u32 subfrmcode;
> + u32 usedslots;
> + u32 msgsl;
> + u32 pending_msgsl;
> + struct mutex m_reconf;
> + u8 *slots;
> +};
> +
> +/*
> + * enum slim_clk_state: Slimbus controller's clock state used internally for
> + * maintaining current clock state.
> + * @SLIM_CLK_ACTIVE: Slimbus clock is active
> + * @SLIM_CLK_PAUSE_FAILED: Slimbus controlled failed to go in clock pause.
> + * Hardware-wise, this state is same as active but controller will wait on
> + * completion before making transition to SLIM_CLK_ACTIVE in framework
> + * @SLIM_CLK_ENTERING_PAUSE: Slimbus clock pause sequence is being sent on the
> + * bus. If this succeeds, state changes to SLIM_CLK_PAUSED. If the
> + * transition fails, state changes to SLIM_CLK_PAUSE_FAILED
> + * @SLIM_CLK_PAUSED: Slimbus controller clock has paused.
> + */
> +enum slim_clk_state {
> + SLIM_CLK_ACTIVE,
> + SLIM_CLK_ENTERING_PAUSE,
> + SLIM_CLK_PAUSE_FAILED,
> + SLIM_CLK_PAUSED,
> +};
> +/*
> + * struct slim_controller: Represents manager for a SlimBUS
> + * (similar to 'master' on I2C)
> + * @dev: Device interface to this driver
> + * @nr: Board-specific number identifier for this controller/bus
> + * @list: Link with other slimbus controllers
> + * @name: Name for this controller
> + * @clkgear: Current clock gear in which this bus is running
> + * @min_cg: Minimum clock gear supported by this controller (default value: 1)
> + * @max_cg: Maximum clock gear supported by this controller (default value: 10)
> + * @clk_state: Controller's clock state from enum slim_clk_state
> + * @pause_comp: Signals completion of clock pause sequence. This is useful when
> + * client tries to call slimbus transaction when controller may be entering
> + * clock pause.
> + * @a_framer: Active framer which is clocking the bus managed by this controller
> + * @m_ctrl: Mutex protecting controller data structures (ports, channels etc)
> + * @addrt: Logical address table
> + * @num_dev: Number of active slimbus slaves on this bus
> + * @txnt: Table of transactions having transaction ID
> + * @last_tid: size of the table txnt (can't grow beyond 256 since TID is 8-bits)
> + * @ports: Ports associated with this controller
> + * @nports: Number of ports supported by the controller
> + * @chans: Channels associated with this controller
> + * @nchans: Number of channels supported
> + * @reserved: Reserved channels that controller wants to use internally
> + * Clients will be assigned channel numbers after this number
> + * @sched: scheduler structure used by the controller
> + * @dev_released: completion used to signal when sysfs has released this
> + * controller so that it can be deleted during shutdown
> + * @xfer_msg: Transfer a message on this controller (this can be a broadcast
> + * control/status message like data channel setup, or a unicast message
> + * like value element read/write.
> + * @set_laddr: Setup logical address at laddr for the slave with elemental
> + * address e_addr. Drivers implementing controller will be expected to
> + * send unicast message to this device with its logical address.
> + * @wakeup: This function pointer implements controller-specific procedure
> + * to wake it up from clock-pause. Framework will call this to bring
> + * the controller out of clock pause.
> + * @config_port: Configure a port and make it ready for data transfer. This is
> + * called by framework after connect_port message is sent successfully.
> + * @framer_handover: If this controller has multiple framers, this API will
> + * be called to switch between framers if controller desires to change
> + * the active framer.
> + * @port_xfer: Called to schedule a transfer on port pn. iobuf is physical
> + * address and the buffer may have to be DMA friendly since data channels
> + * will be using data from this buffers without SW intervention.
> + * @port_xfer_status: Called by framework when client calls get_xfer_status
> + * API. Returns how much buffer is actually processed and the port
> + * errors (e.g. overflow/underflow) if any.
> + */
> +struct slim_controller {
> + struct device dev;
> + unsigned int nr;
> + struct list_head list;
> + char name[SLIMBUS_NAME_SIZE];
> + int clkgear;
> + int min_cg;
> + int max_cg;
> + enum slim_clk_state clk_state;
> + struct completion pause_comp;
> + struct slim_framer *a_framer;
> + struct mutex m_ctrl;
> + struct slim_addrt *addrt;
> + u8 num_dev;
> + struct slim_msg_txn **txnt;
> + u8 last_tid;
> + struct slim_port *ports;
> + int nports;
> + struct slim_ich *chans;
> + int nchans;
> + u8 reserved;
> + struct slim_sched sched;
> + struct completion dev_released;
> + int (*xfer_msg)(struct slim_controller *ctrl,
> + struct slim_msg_txn *txn);
> + int (*set_laddr)(struct slim_controller *ctrl,
> + struct slim_eaddr *ea, u8 *laddr);
> + int (*wakeup)(struct slim_controller *ctrl);
> + int (*config_port)(struct slim_controller *ctrl,
> + u8 port);
> + int (*framer_handover)(struct slim_controller *ctrl,
> + struct slim_framer *new_framer);
> + int (*port_xfer)(struct slim_controller *ctrl,
> + u8 pn, u8 *iobuf, u32 len,
> + struct completion *comp);
> + enum slim_port_err (*port_xfer_status)(struct slim_controller *ctr,
> + u8 pn, u8 **done_buf, u32 *done_len);
> +};
> +#define to_slim_controller(d) container_of(d, struct slim_controller, dev)
> +
> +/*
> + * struct slim_driver: Manage Slimbus generic/slave device driver
> + * @probe: Binds this driver to a slimbus device.
> + * @remove: Unbinds this driver from the slimbus device.
> + * @shutdown: Standard shutdown callback used during powerdown/halt.
> + * @suspend: Standard suspend callback used during system suspend
> + * @resume: Standard resume callback used during system resume
> + * @driver: Slimbus device drivers should initialize name and owner field of
> + * this structure
> + * @id_table: List of slimbus devices supported by this driver
> + */
> +struct slim_driver {
> + int (*probe)(struct slim_device *sldev);
> + int (*remove)(struct slim_device *sldev);
> + void (*shutdown)(struct slim_device *sldev);
> + int (*suspend)(struct slim_device *sldev,
> + pm_message_t pmesg);
> + int (*resume)(struct slim_device *sldev);
> +
> + struct device_driver driver;
> + const struct slim_device_id *id_table;
> +};
> +#define to_slim_driver(d) container_of(d, struct slim_driver, driver)
> +
> +/*
> + * struct slim_pending_ch: List of pending channels used by framework.
> + * @chan: Channel number
> + * @pending: list of channels
> + */
> +struct slim_pending_ch {
> + u8 chan;
> + struct list_head pending;
> +};
> +
> +/*
> + * Client/device handle (struct slim_device):
> + * ------------------------------------------
> + * This is the client/device handle returned when a slimbus
> + * device is registered with a controller. This structure can be provided
> + * during register_board_info, or can be allocated using slim_add_device API.
> + * Pointer to this structure is used by client-driver as a handle.
> + * @dev: Driver model representation of the device.
> + * @name: Name of driver to use with this device.
> + * @e_addr: Elemental address of this device.
> + * @driver: Device's driver. Pointer to access routines.
> + * @ctrl: Slimbus controller managing the bus hosting this device.
> + * @laddr: 1-byte Logical address of this device.
> + * @enumerated: Set when this device reports present. laddr is valid if this
> + * field is set.
> + * @wait_enum: This completion is signalled when device reports. During early
> + * probe, device can wait on this object to indicate successful
> + * enumeration. Note that device should only wait if enumerated
> + * flag was not set.
> + * @mark_define: List of channels pending definition/activation.
> + * @mark_suspend: List of channels pending suspend.
> + * @mark_removal: List of channels pending removal.
> + * @sldev_reconf: Mutex to protect the pending data-channel lists.
> + * @pending_msgsl: Message bandwidth reservation request by this client in
> + * slots that's pending reconfiguration.
> + * @cur_msgsl: Message bandwidth reserved by this client in slots.
> + * These 3 lists are managed by framework. Lists are populated when client
> + * calls channel control API without reconfig-flag set and the lists are
> + * emptied when the reconfiguration is done by this client.
> + */
> +struct slim_device {
> + struct device dev;
> + char *name;
> + struct slim_eaddr e_addr;
> + struct slim_driver *driver;
> + struct slim_controller *ctrl;
> + u8 laddr;
> + bool enumerated;
> + struct completion wait_enum;
> + struct list_head mark_define;
> + struct list_head mark_suspend;
> + struct list_head mark_removal;
> + struct mutex sldev_reconf;
> + u32 pending_msgsl;
> + u32 cur_msgsl;
> +};
> +#define to_slim_device(d) container_of(d, struct slim_device, dev)
> +
> +/*
> + * struct slim_boardinfo: Declare board info for Slimbus device bringup.
> + * @bus_num: Controller number (bus) on which this device will sit.
> + * @slim_slave: Device to be registered with slimbus.
> + */
> +struct slim_boardinfo {
> + int bus_num;
> + struct slim_device *slim_slave;
> +};
> +
> +/*
> + * slim_get_logical_addr: Return the logical address of a slimbus device.
> + * @sb: client handle requesting the adddress.
> + * @e_addr: Elemental address of the device.
> + * @laddr: output buffer to store the address
> + * context: can sleep
> + * -EINVAL is returned in case of invalid parameters, and -ENXIO is returned if
> + * the device with this elemental address is not found.
> + */
> +
> +extern int slim_get_logical_addr(struct slim_device *sb,
> + struct slim_eaddr *e_addr, u8 *laddr);
> +
> +
> +/* Message APIs Unicast message APIs used by slimbus slave drivers */
> +
> +/*
> + * Message API access routines.
> + * @sb: client handle requesting elemental message reads, writes.
> + * @msg: Input structure for start-offset, number of bytes to read.
> + * @rbuf: data buffer to be filled with values read.
> + * @len: data buffer size
> + * @wbuf: data buffer containing value/information to be written
> + * context: can sleep
> + * Returns:
> + * -EINVAL: Invalid parameters
> + * -ETIMEDOUT: If controller could not complete the request. This may happen if
> + * the bus lines are not clocked, controller is not powered-on, slave with
> + * given address is not enumerated/responding.
> + */
> +extern int slim_request_val_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *buf,
> + u8 len);
> +extern int slim_request_inf_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *buf,
> + u8 len);
> +extern int slim_change_val_element(struct slim_device *sb,
> + struct slim_ele_access *msg,
> + const u8 *buf, u8 len);
> +extern int slim_clear_inf_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *buf,
> + u8 len);
> +extern int slim_request_change_val_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *rbuf,
> + const u8 *wbuf, u8 len);
> +extern int slim_request_clear_inf_element(struct slim_device *sb,
> + struct slim_ele_access *msg, u8 *rbuf,
> + const u8 *wbuf, u8 len);
> +
> +/*
> + * Broadcast message API:
> + * call this API directly with sbdev = NULL.
> + * For broadcast reads, make sure that buffers are big-enough to incorporate
> + * replies from all logical addresses.
> + * All controllers may not support broadcast
> + */
> +extern int slim_xfer_msg(struct slim_controller *ctrl,
> + struct slim_device *sbdev, struct slim_ele_access *msg,
> + u16 mc, u8 *rbuf, const u8 *wbuf, u8 len);
> +/* end of message apis */
> +
> +/* Port management for manager device APIs */
> +
> +/*
> + * slim_alloc_mgrports: Allocate port on manager side.
> + * @sb: device/client handle.
> + * @req: Port request type.
> + * @nports: Number of ports requested
> + * @rh: output buffer to store the port handles
> + * @hsz: size of buffer storing handles
> + * context: can sleep
> + * This port will be typically used by SW. e.g. client driver wants to receive
> + * some data from audio codec HW using a data channel.
> + * Port allocated using this API will be used to receive the data.
> + * If half-duplex ports are requested, two adjacent ports are allocated for
> + * 1 half-duplex port. So the handle-buffer size should be twice the number
> + * of half-duplex ports to be allocated.
> + * -EDQUOT is returned if all ports are in use.
> + */
> +extern int slim_alloc_mgrports(struct slim_device *sb, enum slim_port_req req,
> + int nports, u32 *rh, int hsz);
> +
> +/* Deallocate the port(s) allocated using the API above */
> +extern int slim_dealloc_mgrports(struct slim_device *sb, u32 *hdl, int hsz);
> +
> +/*
> + * slim_port_xfer: Schedule buffer to be transferred/received using port-handle.
> + * @sb: client handle
> + * @ph: port-handle
> + * @iobuf: buffer to be transferred or populated
> + * @len: buffer size.
> + * @comp: completion signal to indicate transfer done or error.
> + * context: can sleep
> + * Returns number of bytes transferred/received if used synchronously.
> + * Will return 0 if used asynchronously.
> + * Client will call slim_port_get_xfer_status to get error and/or number of
> + * bytes transferred if used asynchronously.
> + */
> +extern int slim_port_xfer(struct slim_device *sb, u32 ph, u8 *iobuf, u32 len,
> + struct completion *comp);
> +
> +/*
> + * slim_port_get_xfer_status: Poll for port transfers, or get transfer status
> + * after completion is done.
> + * @sb: client handle
> + * @ph: port-handle
> + * @done_buf: return pointer (iobuf from slim_port_xfer) which is processed.
> + * @done_len: Number of bytes transferred.
> + * This can be called when port_xfer completion is signalled.
> + * The API will return port transfer error (underflow/overflow/disconnect)
> + * and/or done_len will reflect number of bytes transferred. Note that
> + * done_len may be valid even if port error (overflow/underflow) has happened.
> + * e.g. If the transfer was scheduled with a few bytes to be transferred and
> + * client has not supplied more data to be transferred, done_len will indicate
> + * number of bytes transferred with underflow error. To avoid frequent underflow
> + * errors, multiple transfers can be queued (e.g. ping-pong buffers) so that
> + * channel has data to be transferred even if client is not ready to transfer
> + * data all the time. done_buf will indicate address of the last buffer
> + * processed from the multiple transfers.
> + */
> +extern enum slim_port_err slim_port_get_xfer_status(struct slim_device *sb,
> + u32 ph, u8 **done_buf, u32 *done_len);
> +
> +/*
> + * slim_connect_src: Connect source port to channel.
> + * @sb: client handle
> + * @srch: source handle to be connected to this channel
> + * @chanh: Channel with which the ports need to be associated with.
> + * Per slimbus specification, a channel may have 1 source port.
> + * Channel specified in chanh needs to be allocated first.
> + * Returns -EALREADY if source is already configured for this channel.
> + * Returns -ENOTCONN if channel is not allocated
> + */
> +extern int slim_connect_src(struct slim_device *sb, u32 srch, u16 chanh);
> +
> +/*
> + * slim_connect_sink: Connect sink port(s) to channel.
> + * @sb: client handle
> + * @sinkh: sink handle(s) to be connected to this channel
> + * @nsink: number of sinks
> + * @chanh: Channel with which the ports need to be associated with.
> + * Per slimbus specification, a channel may have multiple sink-ports.
> + * Channel specified in chanh needs to be allocated first.
> + * Returns -EALREADY if sink is already configured for this channel.
> + * Returns -ENOTCONN if channel is not allocated
> + */
> +extern int slim_connect_sink(struct slim_device *sb, u32 *sinkh, int nsink,
> + u16 chanh);
> +/*
> + * slim_disconnect_ports: Disconnect port(s) from channel
> + * @sb: client handle
> + * @ph: ports to be disconnected
> + * @nph: number of ports.
> + * Disconnects ports from a channel.
> + */
> +extern int slim_disconnect_ports(struct slim_device *sb, u32 *ph, int nph);
> +
> +/*
> + * slim_get_slaveport: Get slave port handle
> + * @la: slave device logical address.
> + * @idx: port index at slave
> + * @rh: return handle
> + * @flw: Flow type (source or destination)
> + * This API only returns a slave port's representation as expected by slimbus
> + * driver. This port is not managed by the slimbus driver. Caller is expected
> + * to have visibility of this port since it's a device-port.
> + */
> +extern int slim_get_slaveport(u8 la, int idx, u32 *rh, enum slim_port_flow flw);
> +
> +
> +/* Channel functions. */
> +
> +/*
> + * slim_alloc_ch: Allocate a slimbus channel and return its handle.
> + * @sb: client handle.
> + * @chanh: return channel handle
> + * Slimbus channels are limited to 256 per specification.
> + * -EXFULL is returned if all channels are in use.
> + * Although slimbus specification supports 256 channels, a controller may not
> + * support that many channels.
> + */
> +extern int slim_alloc_ch(struct slim_device *sb, u16 *chanh);
> +
> +/*
> + * slim_query_ch: Get reference-counted handle for a channel number. Every
> + * channel is reference counted by one as producer and the others as
> + * consumer)
> + * @sb: client handle
> + * @chan: slimbus channel number
> + * @chanh: return channel handle
> + * If request channel number is not in use, it is allocated, and reference
> + * count is set to one. If the channel was was already allocated, this API
> + * will return handle to that channel and reference count is incremented.
> + * -EXFULL is returned if all channels are in use
> + */
> +extern int slim_query_ch(struct slim_device *sb, u8 chan, u16 *chanh);
> +/*
> + * slim_dealloc_ch: Deallocate channel allocated using the API above
> + * -EISCONN is returned if the channel is tried to be deallocated without
> + * being removed first.
> + * -ENOTCONN is returned if deallocation is tried on a channel that's not
> + * allocated.
> + */
> +extern int slim_dealloc_ch(struct slim_device *sb, u16 chanh);
> +
> +
> +/*
> + * slim_define_ch: Define a channel.This API defines channel parameters for a
> + * given channel.
> + * @sb: client handle.
> + * @prop: slim_ch structure with channel parameters desired to be used.
> + * @chanh: list of channels to be defined.
> + * @nchan: number of channels in a group (1 if grp is false)
> + * @grp: Are the channels grouped
> + * @grph: return group handle if grouping of channels is desired.
> + * Channels can be grouped if multiple channels use same parameters
> + * (e.g. 5.1 audio has 6 channels with same parameters. They will all be
> + * grouped and given 1 handle for simplicity and avoid repeatedly calling
> + * the API)
> + * -EISCONN is returned if channel is already used with different parameters.
> + * -ENXIO is returned if the channel is not yet allocated.
> + */
> +extern int slim_define_ch(struct slim_device *sb, struct slim_ch *prop,
> + u16 *chanh, u8 nchan, bool grp, u16 *grph);
> +
> +/*
> + * slim_control_ch: Channel control API.
> + * @sb: client handle
> + * @grpchanh: group or channel handle to be controlled
> + * @chctrl: Control command (activate/suspend/remove)
> + * @commit: flag to indicate whether the control should take effect right-away.
> + * This API activates, removes or suspends a channel (or group of channels)
> + * grpchanh indicates the channel or group handle (returned by the define_ch
> + * API). Reconfiguration may be time-consuming since it can change all other
> + * active channel allocations on the bus, change in clock gear used by the
> + * slimbus, and change in the control space width used for messaging.
> + * commit makes sure that multiple channels can be activated/deactivated before
> + * reconfiguration is started.
> + * -EXFULL is returned if there is no space in TDM to reserve the bandwidth.
> + * -EISCONN/-ENOTCONN is returned if the channel is already connected or not
> + * yet defined.
> + * -EINVAL is returned if individual control of a grouped-channel is attempted.
> + */
> +extern int slim_control_ch(struct slim_device *sb, u16 grpchanh,
> + enum slim_ch_control chctrl, bool commit);
> +
> +/*
> + * slim_get_ch_state: Channel state.
> + * This API returns the channel's state (active, suspended, inactive etc)
> + */
> +extern enum slim_ch_state slim_get_ch_state(struct slim_device *sb,
> + u16 chanh);
> +
> +/*
> + * slim_reservemsg_bw: Request to reserve bandwidth for messages.
> + * @sb: client handle
> + * @bw_bps: message bandwidth in bits per second to be requested
> + * @commit: indicates whether the reconfiguration needs to be acted upon.
> + * This API call can be grouped with slim_control_ch API call with only one of
> + * the APIs specifying the commit flag to avoid reconfiguration being called too
> + * frequently. -EXFULL is returned if there is no space in TDM to reserve the
> + * bandwidth. -EBUSY is returned if reconfiguration is requested, but a request
> + * is already in progress.
> + */
> +extern int slim_reservemsg_bw(struct slim_device *sb, u32 bw_bps, bool commit);
> +
> +/*
> + * slim_reconfigure_now: Request reconfiguration now.
> + * @sb: client handle
> + * This API does what commit flag in other scheduling APIs do.
> + * -EXFULL is returned if there is no space in TDM to reserve the
> + * bandwidth. -EBUSY is returned if reconfiguration request is already in
> + * progress.
> + */
> +extern int slim_reconfigure_now(struct slim_device *sb);
> +
> +/*
> + * slim_ctrl_clk_pause: Called by slimbus controller to request clock to be
> + * paused or woken up out of clock pause
> + * @ctrl: controller requesting bus to be paused or woken up
> + * @wakeup: Wakeup this controller from clock pause.
> + * @restart: Restart time value per spec used for clock pause. This value
> + * isn't used when controller is to be woken up.
> + * This API executes clock pause reconfiguration sequence if wakeup is false.
> + * If wakeup is true, controller's wakeup is called
> + * Slimbus clock is idle and can be disabled by the controller later.
> + */
> +extern int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup,
> + u8 restart);
> +
> +/*
> + * slim_driver_register: Client driver registration with slimbus
> + * @drv:Client driver to be associated with client-device.
> + * This API will register the client driver with the slimbus
> + * It is called from the driver's module-init function.
> + */
> +extern int slim_driver_register(struct slim_driver *drv);
> +
> +/*
> + * slim_add_numbered_controller: Controller bring-up.
> + * @ctrl: Controller to be registered.
> + * A controller is registered with the framework using this API. ctrl->nr is the
> + * desired number with which slimbus framework registers the controller.
> + * Function will return -EBUSY if the number is in use.
> + */
> +extern int slim_add_numbered_controller(struct slim_controller *ctrl);
> +
> +/*
> + * slim_del_controller: Controller tear-down.
> + * Controller added with the above API is teared down using this API.
> + */
> +extern int slim_del_controller(struct slim_controller *ctrl);
> +
> +/*
> + * slim_add_device: Add a new device without register board info.
> + * @ctrl: Controller to which this device is to be added to.
> + * Called when device doesn't have an explicit client-driver to be probed, or
> + * the client-driver is a module installed dynamically.
> + */
> +extern int slim_add_device(struct slim_controller *ctrl,
> + struct slim_device *sbdev);
> +
> +/*
> + * slim_query_device: Query and get handle to a device.
> + * @ctrl: Controller on which this device will be added/queried
> + * @e_addr: Elemental address of the device to be queried
> + * Returns pointer to a device if it has already reported. Creates a new
> + * device and returns pointer to it if the device has not yet enumerated.
> + */
> +struct slim_device *slim_query_device(struct slim_controller *ctrl,
> + struct slim_eaddr *e_addr);
> +
> +/* slim_remove_device: Remove the effect of slim_add_device() */
> +extern void slim_remove_device(struct slim_device *sbdev);
> +
> +/*
> + * slim_assign_laddr: Assign logical address to a device enumerated.
> + * @ctrl: Controller with which device is enumerated.
> + * @e_addr: Elemental address of the device.
> + * @laddr: Return logical address.
> + * Called by controller in response to REPORT_PRESENT. Framework will assign
> + * a logical address to this enumeration address.
> + * Function returns -EXFULL to indicate that all logical addresses are already
> + * taken.
> + */
> +extern int slim_assign_laddr(struct slim_controller *ctrl,
> + struct slim_eaddr *e_addr, u8 *laddr);
> +
> +/*
> + * slim_msg_response: Deliver Message response received from a device to the
> + * framework.
> + * @ctrl: Controller handle
> + * @reply: Reply received from the device
> + * @len: Length of the reply
> + * @tid: Transaction ID received with which framework can associate reply.
> + * Called by controller to inform framework about the response received.
> + * This helps in making the API asynchronous, and controller-driver doesn't need
> + * to manage 1 more table other than the one managed by framework mapping TID
> + * with buffers
> + */
> +extern void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid,
> + u8 len);
> +
> +/*
> + * slim_register_board_info: Board-initialization routine.
> + * @info: List of all devices on all controllers present on the board.
> + * @n: number of entries.
> + * API enumerates respective devices on corresponding controller.
> + * Called from board-init function.
> + */
> +#ifdef CONFIG_SLIMBUS
> +extern int slim_register_board_info(struct slim_boardinfo const *info,
> + unsigned n);
> +#else
> +int slim_register_board_info(struct slim_boardinfo const *info,
> + unsigned n)
> +{
> + return 0;
> +}
> +#endif
> +
> +static inline void *slim_get_ctrldata(const struct slim_controller *dev)
> +{
> + return dev_get_drvdata(&dev->dev);
> +}
> +
> +static inline void slim_set_ctrldata(struct slim_controller *dev, void *data)
> +{
> + dev_set_drvdata(&dev->dev, data);
> +}
> +
> +static inline void *slim_get_devicedata(const struct slim_device *dev)
> +{
> + return dev_get_drvdata(&dev->dev);
> +}
> +
> +static inline void slim_set_clientdata(struct slim_device *dev, void *data)
> +{
> + dev_set_drvdata(&dev->dev, data);
> +}
> +#endif /* _LINUX_SLIMBUS_H */
The patch below fixes an bug where devices that have not be matched
to a driver will result in a segv when the bus is shutdown.
I am not sure what the policy on patches-on-patches is, it just
doesn't seem workable. So I this will be the first and last. Instead I
am maintaing a git repository that tracks Linus's tree:
git://git.plastictigers.com/slimbus.git
or it can be browsed via the web:
http://git.plastictigers.com/
Currently there are 3 branches:
slimbus-rfc-patch : the original rfc patch (for reference)
slimbus-patch : the current patch (as posted by Sagar)
slimbus-patch-fixes : slimbus-patch + fixes (like the one below)
There will likely be a couple of new branches in the future.
If anyone has a suggestion on a better way of doing this I am all
ears.
---
drivers/slimbus/slimbus.c | 3 +++
1 files changed, 3 insertions(+), 0 deletions(-)
diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c
index 6d20241..673222c 100644
--- a/drivers/slimbus/slimbus.c
+++ b/drivers/slimbus/slimbus.c
@@ -125,6 +125,9 @@ static void slim_device_shutdown(struct device *dev)
struct slim_device *slim_dev;
struct slim_driver *driver;
+ if (!dev->driver)
+ return;
+
if (dev->type == &slim_dev_type)
slim_dev = to_slim_device(dev);
else
--
1.7.7
> I understand it is not a 1-to-1 mapping. However it *is* used as such:
>
> wbuf[2] = (u8)((segdist & 0xF00) >> 8) | (slc->prop.prot << 4);
>
> which results in NEXT_DEFINE_CHANNEL messages with an invalid TP
> field.
Yes, thanks for catching this. I will make changes to rectify this.
>
> Yes, my mistake. The driver wouldn't have to poll if there was another
> callback. So I don't see how the completion mechanism is superior: it
> forces a synchronous interface to asynchronous events, or the driver
> developer has to work around it.
The get_logical_address API itself doesn't take completion. Completion is
part of slim_device structure so that the slim_device's driver can wait on
it whenever it needs LA for the first time. (Framework signals completion
on a slim_device when it gets enumerated)
If callback is preferred, I will change it to callback.
Regards-
Sagar
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
>
> Hi Sagar. A few comments below. I have only done a quick read through.
>
> ~Ryan
Thank you Ryan for taking time to review this. I agree with most of the
comments and will take care of them when I submit the next patch.
Regards
Sagar