Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# SPDX-License-Identifier: GPL-2.0
#
# SLIMbus driver configuration
#
menuconfig SLIMBUS
tristate "SLIMbus support"
help
SLIMbus is standard interface between System-on-Chip and audio codec,
and other peripheral components in typical embedded systems.
If unsure, choose N.
if SLIMBUS
# SLIMbus controllers
config SLIM_QCOM_NGD_CTRL
tristate "Qualcomm SLIMbus Satellite Non-Generic Device Component"
depends on HAS_IOMEM && DMA_ENGINE && NET
depends on QCOM_RPROC_COMMON || (COMPILE_TEST && !QCOM_RPROC_COMMON)
depends on ARCH_QCOM || COMPILE_TEST
select QCOM_QMI_HELPERS
select QCOM_PDR_HELPERS
help
Select driver if Qualcomm's SLIMbus Satellite Non-Generic Device
Component is programmed using Linux kernel.
This is light-weight slimbus controller driver responsible for
communicating with slave HW directly over the bus using messaging
interface, and communicating with master component residing on ADSP
for bandwidth and data-channel management.
endif
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for kernel SLIMbus framework.
#
obj-$(CONFIG_SLIMBUS) += slimbus.o
slimbus-y := core.o messaging.o sched.o stream.o
#Controllers
obj-$(CONFIG_SLIM_QCOM_NGD_CTRL) += slim-qcom-ngd-ctrl.o
slim-qcom-ngd-ctrl-y := qcom-ngd-ctrl.o
+547
View File
@@ -0,0 +1,547 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2011-2017, The Linux Foundation
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/idr.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/slimbus.h>
#include "slimbus.h"
static DEFINE_IDA(ctrl_ida);
static const struct slim_device_id *slim_match(const struct slim_device_id *id,
const struct slim_device *sbdev)
{
while (id->manf_id != 0 || id->prod_code != 0) {
if (id->manf_id == sbdev->e_addr.manf_id &&
id->prod_code == sbdev->e_addr.prod_code &&
id->dev_index == sbdev->e_addr.dev_index &&
id->instance == sbdev->e_addr.instance)
return id;
id++;
}
return NULL;
}
static int slim_device_match(struct device *dev, const struct device_driver *drv)
{
struct slim_device *sbdev = to_slim_device(dev);
const struct slim_driver *sbdrv = to_slim_driver(drv);
/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;
return !!slim_match(sbdrv->id_table, sbdev);
}
static void slim_device_update_status(struct slim_device *sbdev,
enum slim_device_status status)
{
struct slim_driver *sbdrv;
if (sbdev->status == status)
return;
sbdev->status = status;
if (!sbdev->dev.driver)
return;
sbdrv = to_slim_driver(sbdev->dev.driver);
if (sbdrv->device_status)
sbdrv->device_status(sbdev, sbdev->status);
}
static int slim_device_probe(struct device *dev)
{
struct slim_device *sbdev = to_slim_device(dev);
struct slim_driver *sbdrv = to_slim_driver(dev->driver);
int ret;
ret = sbdrv->probe(sbdev);
if (ret)
return ret;
/* try getting the logical address after probe */
ret = slim_get_logical_addr(sbdev);
if (!ret) {
slim_device_update_status(sbdev, SLIM_DEVICE_STATUS_UP);
} else {
dev_err(&sbdev->dev, "Failed to get logical address\n");
ret = -EPROBE_DEFER;
}
return ret;
}
static void slim_device_remove(struct device *dev)
{
struct slim_device *sbdev = to_slim_device(dev);
struct slim_driver *sbdrv;
if (dev->driver) {
sbdrv = to_slim_driver(dev->driver);
if (sbdrv->remove)
sbdrv->remove(sbdev);
}
}
static int slim_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct slim_device *sbdev = to_slim_device(dev);
return add_uevent_var(env, "MODALIAS=slim:%s", dev_name(&sbdev->dev));
}
const struct bus_type slimbus_bus = {
.name = "slimbus",
.match = slim_device_match,
.probe = slim_device_probe,
.remove = slim_device_remove,
.uevent = slim_device_uevent,
};
EXPORT_SYMBOL_GPL(slimbus_bus);
/*
* __slim_driver_register() - Client driver registration with SLIMbus
*
* @drv:Client driver to be associated with client-device.
* @owner: owning module/driver
*
* 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, struct module *owner)
{
/* ID table and probe are mandatory */
if (!(drv->driver.of_match_table || drv->id_table) || !drv->probe)
return -EINVAL;
drv->driver.bus = &slimbus_bus;
drv->driver.owner = owner;
return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__slim_driver_register);
/*
* slim_driver_unregister() - Undo effect of slim_driver_register
*
* @drv: Client driver to be unregistered
*/
void slim_driver_unregister(struct slim_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(slim_driver_unregister);
static void slim_dev_release(struct device *dev)
{
struct slim_device *sbdev = to_slim_device(dev);
of_node_put(sbdev->dev.of_node);
kfree(sbdev);
}
static int slim_add_device(struct slim_controller *ctrl,
struct slim_device *sbdev,
struct device_node *node)
{
sbdev->dev.bus = &slimbus_bus;
sbdev->dev.parent = ctrl->dev;
sbdev->dev.release = slim_dev_release;
sbdev->dev.driver = NULL;
sbdev->ctrl = ctrl;
INIT_LIST_HEAD(&sbdev->stream_list);
spin_lock_init(&sbdev->stream_list_lock);
sbdev->dev.of_node = of_node_get(node);
sbdev->dev.fwnode = of_fwnode_handle(node);
dev_set_name(&sbdev->dev, "%x:%x:%x:%x",
sbdev->e_addr.manf_id,
sbdev->e_addr.prod_code,
sbdev->e_addr.dev_index,
sbdev->e_addr.instance);
return device_register(&sbdev->dev);
}
static struct slim_device *slim_alloc_device(struct slim_controller *ctrl,
struct slim_eaddr *eaddr,
struct device_node *node)
{
struct slim_device *sbdev;
int ret;
sbdev = kzalloc_obj(*sbdev);
if (!sbdev)
return NULL;
sbdev->e_addr = *eaddr;
ret = slim_add_device(ctrl, sbdev, node);
if (ret) {
put_device(&sbdev->dev);
return NULL;
}
return sbdev;
}
static void of_register_slim_devices(struct slim_controller *ctrl)
{
struct device *dev = ctrl->dev;
struct device_node *node;
if (!ctrl->dev->of_node)
return;
for_each_child_of_node(ctrl->dev->of_node, node) {
struct slim_device *sbdev;
struct slim_eaddr e_addr;
const char *compat = NULL;
int reg[2], ret;
int manf_id, prod_code;
compat = of_get_property(node, "compatible", NULL);
if (!compat)
continue;
ret = sscanf(compat, "slim%x,%x", &manf_id, &prod_code);
if (ret != 2) {
dev_err(dev, "Manf ID & Product code not found %s\n",
compat);
continue;
}
ret = of_property_read_u32_array(node, "reg", reg, 2);
if (ret) {
dev_err(dev, "Device and Instance id not found:%d\n",
ret);
continue;
}
e_addr.dev_index = reg[0];
e_addr.instance = reg[1];
e_addr.manf_id = manf_id;
e_addr.prod_code = prod_code;
sbdev = slim_alloc_device(ctrl, &e_addr, node);
if (!sbdev)
continue;
}
}
/*
* slim_register_controller() - Controller bring-up and registration.
*
* @ctrl: Controller to be registered.
*
* A controller is registered with the framework using this API.
* If devices on a controller were registered before controller,
* this will make sure that they get probed when controller is up
*/
int slim_register_controller(struct slim_controller *ctrl)
{
int id;
id = ida_alloc(&ctrl_ida, GFP_KERNEL);
if (id < 0)
return id;
ctrl->id = id;
if (!ctrl->min_cg)
ctrl->min_cg = SLIM_MIN_CLK_GEAR;
if (!ctrl->max_cg)
ctrl->max_cg = SLIM_MAX_CLK_GEAR;
ida_init(&ctrl->laddr_ida);
idr_init(&ctrl->tid_idr);
mutex_init(&ctrl->lock);
mutex_init(&ctrl->sched.m_reconf);
init_completion(&ctrl->sched.pause_comp);
spin_lock_init(&ctrl->txn_lock);
dev_dbg(ctrl->dev, "Bus [%s] registered:dev:%p\n",
ctrl->name, ctrl->dev);
of_register_slim_devices(ctrl);
return 0;
}
EXPORT_SYMBOL_GPL(slim_register_controller);
/* slim_remove_device: Remove the effect of slim_add_device() */
static void slim_remove_device(struct slim_device *sbdev)
{
device_unregister(&sbdev->dev);
}
static int slim_ctrl_remove_device(struct device *dev, void *null)
{
slim_remove_device(to_slim_device(dev));
return 0;
}
/**
* slim_unregister_controller() - Controller tear-down.
*
* @ctrl: Controller to tear-down.
*/
int slim_unregister_controller(struct slim_controller *ctrl)
{
/* Remove all clients */
device_for_each_child(ctrl->dev, NULL, slim_ctrl_remove_device);
ida_free(&ctrl_ida, ctrl->id);
return 0;
}
EXPORT_SYMBOL_GPL(slim_unregister_controller);
/**
* slim_report_absent() - Controller calls this function when a device
* reports absent, OR when the device cannot be communicated with
*
* @sbdev: Device that cannot be reached, or sent report absent
*/
void slim_report_absent(struct slim_device *sbdev)
{
struct slim_controller *ctrl = sbdev->ctrl;
if (!ctrl)
return;
/* invalidate logical addresses */
mutex_lock(&ctrl->lock);
sbdev->is_laddr_valid = false;
mutex_unlock(&ctrl->lock);
if (!ctrl->get_laddr)
ida_free(&ctrl->laddr_ida, sbdev->laddr);
slim_device_update_status(sbdev, SLIM_DEVICE_STATUS_DOWN);
}
EXPORT_SYMBOL_GPL(slim_report_absent);
static bool slim_eaddr_equal(const struct slim_eaddr *a,
const struct slim_eaddr *b)
{
return (a->manf_id == b->manf_id &&
a->prod_code == b->prod_code &&
a->dev_index == b->dev_index &&
a->instance == b->instance);
}
static int slim_match_dev(struct device *dev, const void *data)
{
const struct slim_eaddr *e_addr = data;
struct slim_device *sbdev = to_slim_device(dev);
return slim_eaddr_equal(&sbdev->e_addr, e_addr);
}
static struct slim_device *find_slim_device(struct slim_controller *ctrl,
struct slim_eaddr *eaddr)
{
struct slim_device *sbdev;
struct device *dev;
dev = device_find_child(ctrl->dev, eaddr, slim_match_dev);
if (dev) {
sbdev = to_slim_device(dev);
return sbdev;
}
return NULL;
}
/**
* slim_get_device() - get handle to a device.
*
* @ctrl: Controller on which this device will be added/queried
* @e_addr: Enumeration address of the device to be queried
*
* Takes a reference to the embedded struct device which needs to be dropped
* after use.
*
* Return: 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_get_device(struct slim_controller *ctrl,
struct slim_eaddr *e_addr)
{
struct slim_device *sbdev;
sbdev = find_slim_device(ctrl, e_addr);
if (!sbdev) {
sbdev = slim_alloc_device(ctrl, e_addr, NULL);
if (!sbdev)
return ERR_PTR(-ENOMEM);
get_device(&sbdev->dev);
}
return sbdev;
}
EXPORT_SYMBOL_GPL(slim_get_device);
/**
* of_slim_get_device() - get handle to a device using dt node.
*
* @ctrl: Controller on which this device will be queried
* @np: node pointer to device
*
* Takes a reference to the embedded struct device which needs to be dropped
* after use.
*
* Return: pointer to a device if it has been registered, otherwise NULL.
*/
struct slim_device *of_slim_get_device(struct slim_controller *ctrl,
struct device_node *np)
{
struct slim_device *sbdev;
struct device *dev;
dev = device_find_child(ctrl->dev, np, device_match_of_node);
if (dev) {
sbdev = to_slim_device(dev);
return sbdev;
}
return NULL;
}
EXPORT_SYMBOL_GPL(of_slim_get_device);
static int slim_device_alloc_laddr(struct slim_device *sbdev,
bool report_present)
{
struct slim_controller *ctrl = sbdev->ctrl;
u8 laddr;
int ret;
mutex_lock(&ctrl->lock);
if (ctrl->get_laddr) {
ret = ctrl->get_laddr(ctrl, &sbdev->e_addr, &laddr);
if (ret < 0)
goto err;
} else if (report_present) {
ret = ida_alloc_max(&ctrl->laddr_ida,
SLIM_LA_MANAGER - 1, GFP_KERNEL);
if (ret < 0)
goto err;
laddr = ret;
} else {
ret = -EINVAL;
goto err;
}
if (ctrl->set_laddr) {
ret = ctrl->set_laddr(ctrl, &sbdev->e_addr, laddr);
if (ret) {
ret = -EINVAL;
goto err;
}
}
sbdev->laddr = laddr;
sbdev->is_laddr_valid = true;
mutex_unlock(&ctrl->lock);
slim_device_update_status(sbdev, SLIM_DEVICE_STATUS_UP);
dev_dbg(ctrl->dev, "setting slimbus l-addr:%x, ea:%x,%x,%x,%x\n",
laddr, sbdev->e_addr.manf_id, sbdev->e_addr.prod_code,
sbdev->e_addr.dev_index, sbdev->e_addr.instance);
return 0;
err:
mutex_unlock(&ctrl->lock);
return ret;
}
/**
* slim_device_report_present() - Report enumerated device.
*
* @ctrl: Controller with which device is enumerated.
* @e_addr: Enumeration address of the device.
* @laddr: Return logical address (if valid flag is false)
*
* 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_device_report_present(struct slim_controller *ctrl,
struct slim_eaddr *e_addr, u8 *laddr)
{
struct slim_device *sbdev;
int ret;
ret = pm_runtime_get_sync(ctrl->dev);
if (ctrl->sched.clk_state != SLIM_CLK_ACTIVE) {
dev_err(ctrl->dev, "slim ctrl not active,state:%d, ret:%d\n",
ctrl->sched.clk_state, ret);
goto out_put_rpm;
}
sbdev = slim_get_device(ctrl, e_addr);
if (IS_ERR(sbdev)) {
ret = -ENODEV;
goto out_put_rpm;
}
if (sbdev->is_laddr_valid) {
*laddr = sbdev->laddr;
ret = 0;
} else {
ret = slim_device_alloc_laddr(sbdev, true);
}
put_device(&sbdev->dev);
out_put_rpm:
pm_runtime_mark_last_busy(ctrl->dev);
pm_runtime_put_autosuspend(ctrl->dev);
return ret;
}
EXPORT_SYMBOL_GPL(slim_device_report_present);
/**
* slim_get_logical_addr() - get/allocate logical address of a SLIMbus device.
*
* @sbdev: client handle requesting the address.
*
* Return: zero if a logical address is valid or a new logical address
* has been assigned. error code in case of error.
*/
int slim_get_logical_addr(struct slim_device *sbdev)
{
if (!sbdev->is_laddr_valid)
return slim_device_alloc_laddr(sbdev, false);
return 0;
}
EXPORT_SYMBOL_GPL(slim_get_logical_addr);
static void __exit slimbus_exit(void)
{
bus_unregister(&slimbus_bus);
}
module_exit(slimbus_exit);
static int __init slimbus_init(void)
{
return bus_register(&slimbus_bus);
}
postcore_initcall(slimbus_init);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("SLIMbus core");
+365
View File
@@ -0,0 +1,365 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2011-2017, The Linux Foundation
*/
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include "slimbus.h"
/**
* slim_msg_response() - Deliver Message response received from a device to the
* framework.
*
* @ctrl: Controller handle
* @reply: Reply received from the device
* @tid: Transaction ID received with which framework can associate reply.
* @len: Length of the 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)
{
struct slim_msg_txn *txn;
struct slim_val_inf *msg;
unsigned long flags;
spin_lock_irqsave(&ctrl->txn_lock, flags);
txn = idr_find(&ctrl->tid_idr, tid);
spin_unlock_irqrestore(&ctrl->txn_lock, flags);
if (txn == NULL)
return;
msg = txn->msg;
if (msg == NULL || msg->rbuf == NULL) {
dev_err(ctrl->dev, "Got response to invalid TID:%d, len:%d\n",
tid, len);
return;
}
slim_free_txn_tid(ctrl, txn);
memcpy(msg->rbuf, reply, len);
if (txn->comp)
complete(txn->comp);
/* Remove runtime-pm vote now that response was received for TID txn */
pm_runtime_mark_last_busy(ctrl->dev);
pm_runtime_put_autosuspend(ctrl->dev);
}
EXPORT_SYMBOL_GPL(slim_msg_response);
/**
* slim_alloc_txn_tid() - Allocate a tid to txn
*
* @ctrl: Controller handle
* @txn: transaction to be allocated with tid.
*
* Return: zero on success with valid txn->tid and error code on failures.
*/
int slim_alloc_txn_tid(struct slim_controller *ctrl, struct slim_msg_txn *txn)
{
unsigned long flags;
int ret = 0;
spin_lock_irqsave(&ctrl->txn_lock, flags);
ret = idr_alloc_cyclic(&ctrl->tid_idr, txn, 1,
SLIM_MAX_TIDS, GFP_ATOMIC);
if (ret < 0) {
spin_unlock_irqrestore(&ctrl->txn_lock, flags);
return ret;
}
txn->tid = ret;
spin_unlock_irqrestore(&ctrl->txn_lock, flags);
return 0;
}
EXPORT_SYMBOL_GPL(slim_alloc_txn_tid);
/**
* slim_free_txn_tid() - Free tid of txn
*
* @ctrl: Controller handle
* @txn: transaction whose tid should be freed
*/
void slim_free_txn_tid(struct slim_controller *ctrl, struct slim_msg_txn *txn)
{
unsigned long flags;
spin_lock_irqsave(&ctrl->txn_lock, flags);
idr_remove(&ctrl->tid_idr, txn->tid);
spin_unlock_irqrestore(&ctrl->txn_lock, flags);
}
EXPORT_SYMBOL_GPL(slim_free_txn_tid);
/**
* slim_do_transfer() - Process a SLIMbus-messaging transaction
*
* @ctrl: Controller handle
* @txn: Transaction to be sent over SLIMbus
*
* Called by controller to transmit messaging transactions not dealing with
* Interface/Value elements. (e.g. transmitting a message to assign logical
* address to a slave device
*
* Return: -ETIMEDOUT: If transmission of this message timed out
* (e.g. due to bus lines not being clocked or driven by controller)
*/
int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn)
{
DECLARE_COMPLETION_ONSTACK(done);
bool need_tid = false, clk_pause_msg = false;
int ret;
unsigned long time_left;
/*
* do not vote for runtime-PM if the transactions are part of clock
* pause sequence
*/
if (ctrl->sched.clk_state == SLIM_CLK_ENTERING_PAUSE &&
(txn->mt == SLIM_MSG_MT_CORE &&
txn->mc >= SLIM_MSG_MC_BEGIN_RECONFIGURATION &&
txn->mc <= SLIM_MSG_MC_RECONFIGURE_NOW))
clk_pause_msg = true;
if (!clk_pause_msg) {
ret = pm_runtime_get_sync(ctrl->dev);
if (ctrl->sched.clk_state != SLIM_CLK_ACTIVE) {
dev_err(ctrl->dev, "ctrl wrong state:%d, ret:%d\n",
ctrl->sched.clk_state, ret);
goto slim_xfer_err;
}
}
/* Initialize tid to invalid value */
txn->tid = 0;
need_tid = slim_tid_txn(txn->mt, txn->mc);
if (need_tid) {
ret = slim_alloc_txn_tid(ctrl, txn);
if (ret)
return ret;
if (!txn->msg->comp)
txn->comp = &done;
}
ret = ctrl->xfer_msg(ctrl, txn);
if (ret == -ETIMEDOUT) {
slim_free_txn_tid(ctrl, txn);
} else if (!ret && need_tid && !txn->msg->comp) {
unsigned long ms = txn->rl + HZ;
time_left = wait_for_completion_timeout(txn->comp,
msecs_to_jiffies(ms));
if (!time_left) {
ret = -ETIMEDOUT;
slim_free_txn_tid(ctrl, txn);
}
}
if (ret)
dev_err(ctrl->dev, "Tx:MT:0x%x, MC:0x%x, LA:0x%x failed:%d\n",
txn->mt, txn->mc, txn->la, ret);
slim_xfer_err:
if (!clk_pause_msg && (txn->tid == 0 || ret == -ETIMEDOUT)) {
/*
* remove runtime-pm vote if this was TX only, or
* if there was error during this transaction
*/
pm_runtime_mark_last_busy(ctrl->dev);
pm_runtime_put_autosuspend(ctrl->dev);
}
return ret;
}
EXPORT_SYMBOL_GPL(slim_do_transfer);
static int slim_val_inf_sanity(struct slim_controller *ctrl,
struct slim_val_inf *msg, u8 mc)
{
if (!msg || msg->num_bytes > 16 ||
(msg->start_offset + msg->num_bytes) > 0xC00)
goto reterr;
switch (mc) {
case SLIM_MSG_MC_REQUEST_VALUE:
case SLIM_MSG_MC_REQUEST_INFORMATION:
if (msg->rbuf != NULL)
return 0;
break;
case SLIM_MSG_MC_CHANGE_VALUE:
case SLIM_MSG_MC_CLEAR_INFORMATION:
if (msg->wbuf != NULL)
return 0;
break;
case SLIM_MSG_MC_REQUEST_CHANGE_VALUE:
case SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION:
if (msg->rbuf != NULL && msg->wbuf != NULL)
return 0;
break;
}
reterr:
if (msg)
dev_err(ctrl->dev, "Sanity check failed:msg:offset:0x%x, mc:%d\n",
msg->start_offset, mc);
return -EINVAL;
}
static u16 slim_slicesize(int code)
{
static const u8 sizetocode[16] = {
0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7
};
code = clamp(code, 1, (int)ARRAY_SIZE(sizetocode));
return sizetocode[code - 1];
}
/**
* slim_xfer_msg() - Transfer a value info message on slim device
*
* @sbdev: slim device to which this msg has to be transferred
* @msg: value info message pointer
* @mc: message code of the message
*
* Called by drivers which want to transfer a vlaue or info elements.
*
* Return: -ETIMEDOUT: If transmission of this message timed out
*/
int slim_xfer_msg(struct slim_device *sbdev, struct slim_val_inf *msg,
u8 mc)
{
DEFINE_SLIM_LDEST_TXN(txn_stack, mc, 6, sbdev->laddr, msg);
struct slim_msg_txn *txn = &txn_stack;
struct slim_controller *ctrl = sbdev->ctrl;
int ret;
u16 sl;
if (!ctrl)
return -EINVAL;
ret = slim_val_inf_sanity(ctrl, msg, mc);
if (ret)
return ret;
sl = slim_slicesize(msg->num_bytes);
dev_dbg(ctrl->dev, "SB xfer msg:os:%x, len:%d, MC:%x, sl:%x\n",
msg->start_offset, msg->num_bytes, mc, sl);
txn->ec = ((sl | (1 << 3)) | ((msg->start_offset & 0xFFF) << 4));
switch (mc) {
case SLIM_MSG_MC_REQUEST_CHANGE_VALUE:
case SLIM_MSG_MC_CHANGE_VALUE:
case SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION:
case SLIM_MSG_MC_CLEAR_INFORMATION:
txn->rl += msg->num_bytes;
break;
default:
break;
}
if (slim_tid_txn(txn->mt, txn->mc))
txn->rl++;
return slim_do_transfer(ctrl, txn);
}
EXPORT_SYMBOL_GPL(slim_xfer_msg);
static void slim_fill_msg(struct slim_val_inf *msg, u32 addr,
size_t count, u8 *rbuf, u8 *wbuf)
{
msg->start_offset = addr;
msg->num_bytes = count;
msg->rbuf = rbuf;
msg->wbuf = wbuf;
msg->comp = NULL;
}
/**
* slim_read() - Read SLIMbus value element
*
* @sdev: client handle.
* @addr: address of value element to read.
* @count: number of bytes to read. Maximum bytes allowed are 16.
* @val: will return what the value element value was
*
* Return: -EINVAL for Invalid parameters, -ETIMEDOUT If transmission of
* this message timed out (e.g. due to bus lines not being clocked
* or driven by controller)
*/
int slim_read(struct slim_device *sdev, u32 addr, size_t count, u8 *val)
{
struct slim_val_inf msg;
slim_fill_msg(&msg, addr, count, val, NULL);
return slim_xfer_msg(sdev, &msg, SLIM_MSG_MC_REQUEST_VALUE);
}
EXPORT_SYMBOL_GPL(slim_read);
/**
* slim_readb() - Read byte from SLIMbus value element
*
* @sdev: client handle.
* @addr: address in the value element to read.
*
* Return: byte value of value element.
*/
int slim_readb(struct slim_device *sdev, u32 addr)
{
int ret;
u8 buf;
ret = slim_read(sdev, addr, 1, &buf);
if (ret < 0)
return ret;
else
return buf;
}
EXPORT_SYMBOL_GPL(slim_readb);
/**
* slim_write() - Write SLIMbus value element
*
* @sdev: client handle.
* @addr: address in the value element to write.
* @count: number of bytes to write. Maximum bytes allowed are 16.
* @val: value to write to value element
*
* Return: -EINVAL for Invalid parameters, -ETIMEDOUT If transmission of
* this message timed out (e.g. due to bus lines not being clocked
* or driven by controller)
*/
int slim_write(struct slim_device *sdev, u32 addr, size_t count, u8 *val)
{
struct slim_val_inf msg;
slim_fill_msg(&msg, addr, count, NULL, val);
return slim_xfer_msg(sdev, &msg, SLIM_MSG_MC_CHANGE_VALUE);
}
EXPORT_SYMBOL_GPL(slim_write);
/**
* slim_writeb() - Write byte to SLIMbus value element
*
* @sdev: client handle.
* @addr: address of value element to write.
* @value: value to write to value element
*
* Return: -EINVAL for Invalid parameters, -ETIMEDOUT If transmission of
* this message timed out (e.g. due to bus lines not being clocked
* or driven by controller)
*
*/
int slim_writeb(struct slim_device *sdev, u32 addr, u8 value)
{
return slim_write(sdev, addr, 1, &value);
}
EXPORT_SYMBOL_GPL(slim_writeb);
File diff suppressed because it is too large Load Diff
+121
View File
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2011-2017, The Linux Foundation
*/
#include <linux/errno.h>
#include "slimbus.h"
/**
* slim_ctrl_clk_pause() - Called by slimbus controller to enter/exit
* '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.
*
* Slimbus specification needs this sequence to turn-off clocks for the bus.
* The sequence involves sending 3 broadcast messages (reconfiguration
* sequence) to inform all devices on the bus.
* To exit clock-pause, controller typically wakes up active framer device.
* This API executes clock pause reconfiguration sequence if wakeup is false.
* If wakeup is true, controller's wakeup is called.
* For entering clock-pause, -EBUSY is returned if a message txn in pending.
*/
int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, u8 restart)
{
int i, ret = 0;
unsigned long flags;
struct slim_sched *sched = &ctrl->sched;
struct slim_val_inf msg = {0, 0, NULL, NULL};
DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION,
3, SLIM_LA_MANAGER, &msg);
if (wakeup == false && restart > SLIM_CLK_UNSPECIFIED)
return -EINVAL;
mutex_lock(&sched->m_reconf);
if (wakeup) {
if (sched->clk_state == SLIM_CLK_ACTIVE) {
mutex_unlock(&sched->m_reconf);
return 0;
}
/*
* Fine-tune calculation based on clock gear,
* message-bandwidth after bandwidth management
*/
ret = wait_for_completion_timeout(&sched->pause_comp,
msecs_to_jiffies(100));
if (!ret) {
mutex_unlock(&sched->m_reconf);
pr_err("Previous clock pause did not finish");
return -ETIMEDOUT;
}
ret = 0;
/*
* Slimbus framework will call controller wakeup
* Controller should make sure that it sets active framer
* out of clock pause
*/
if (sched->clk_state == SLIM_CLK_PAUSED && ctrl->wakeup)
ret = ctrl->wakeup(ctrl);
if (!ret)
sched->clk_state = SLIM_CLK_ACTIVE;
mutex_unlock(&sched->m_reconf);
return ret;
}
/* already paused */
if (ctrl->sched.clk_state == SLIM_CLK_PAUSED) {
mutex_unlock(&sched->m_reconf);
return 0;
}
spin_lock_irqsave(&ctrl->txn_lock, flags);
for (i = 0; i < SLIM_MAX_TIDS; i++) {
/* Pending response for a message */
if (idr_find(&ctrl->tid_idr, i)) {
spin_unlock_irqrestore(&ctrl->txn_lock, flags);
mutex_unlock(&sched->m_reconf);
return -EBUSY;
}
}
spin_unlock_irqrestore(&ctrl->txn_lock, flags);
sched->clk_state = SLIM_CLK_ENTERING_PAUSE;
/* clock pause sequence */
ret = slim_do_transfer(ctrl, &txn);
if (ret)
goto clk_pause_ret;
txn.mc = SLIM_MSG_MC_NEXT_PAUSE_CLOCK;
txn.rl = 4;
msg.num_bytes = 1;
msg.wbuf = &restart;
ret = slim_do_transfer(ctrl, &txn);
if (ret)
goto clk_pause_ret;
txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW;
txn.rl = 3;
msg.num_bytes = 1;
msg.wbuf = NULL;
ret = slim_do_transfer(ctrl, &txn);
clk_pause_ret:
if (ret) {
sched->clk_state = SLIM_CLK_ACTIVE;
} else {
sched->clk_state = SLIM_CLK_PAUSED;
complete(&sched->pause_comp);
}
mutex_unlock(&sched->m_reconf);
return ret;
}
EXPORT_SYMBOL_GPL(slim_ctrl_clk_pause);
+453
View File
@@ -0,0 +1,453 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2011-2017, The Linux Foundation
*/
#ifndef _DRIVERS_SLIMBUS_H
#define _DRIVERS_SLIMBUS_H
#include <linux/module.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/completion.h>
#include <linux/slimbus.h>
/* 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)
/* SLIMbus message types. Related to interpretation of message code. */
#define SLIM_MSG_MT_CORE 0x0
#define SLIM_MSG_MT_DEST_REFERRED_USER 0x2
#define SLIM_MSG_MT_SRC_REFERRED_USER 0x6
/*
* SLIM Broadcast header format
* BYTE 0: MT[7:5] RL[4:0]
* BYTE 1: RSVD[7] MC[6:0]
* BYTE 2: RSVD[7:6] DT[5:4] PI[3:0]
*/
#define SLIM_MSG_MT_MASK GENMASK(2, 0)
#define SLIM_MSG_MT_SHIFT 5
#define SLIM_MSG_RL_MASK GENMASK(4, 0)
#define SLIM_MSG_RL_SHIFT 0
#define SLIM_MSG_MC_MASK GENMASK(6, 0)
#define SLIM_MSG_MC_SHIFT 0
#define SLIM_MSG_DT_MASK GENMASK(1, 0)
#define SLIM_MSG_DT_SHIFT 4
#define SLIM_HEADER_GET_MT(b) ((b >> SLIM_MSG_MT_SHIFT) & SLIM_MSG_MT_MASK)
#define SLIM_HEADER_GET_RL(b) ((b >> SLIM_MSG_RL_SHIFT) & SLIM_MSG_RL_MASK)
#define SLIM_HEADER_GET_MC(b) ((b >> SLIM_MSG_MC_SHIFT) & SLIM_MSG_MC_MASK)
#define SLIM_HEADER_GET_DT(b) ((b >> SLIM_MSG_DT_SHIFT) & SLIM_MSG_DT_MASK)
/* Device management messages used by this framework */
#define SLIM_MSG_MC_REPORT_PRESENT 0x1
#define SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS 0x2
#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
/* Clock pause Reconfiguration messages */
#define SLIM_MSG_MC_BEGIN_RECONFIGURATION 0x40
#define SLIM_MSG_MC_NEXT_PAUSE_CLOCK 0x4A
#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 values per SLIMbus spec */
#define SLIM_CLK_FAST 0
#define SLIM_CLK_CONST_PHASE 1
#define SLIM_CLK_UNSPECIFIED 2
/* Destination type Values */
#define SLIM_MSG_DEST_LOGICALADDR 0
#define SLIM_MSG_DEST_ENUMADDR 1
#define SLIM_MSG_DEST_BROADCAST 3
/* Standard values per SLIMbus spec needed by controllers and devices */
#define SLIM_MAX_CLK_GEAR 10
#define SLIM_MIN_CLK_GEAR 1
#define SLIM_SLOT_LEN_BITS 4
/* Indicate that the frequency of the flow and the bus frequency are locked */
#define SLIM_CHANNEL_CONTENT_FL BIT(7)
/* Standard values per SLIMbus spec needed by controllers and devices */
#define SLIM_CL_PER_SUPERFRAME 6144
#define SLIM_SLOTS_PER_SUPERFRAME (SLIM_CL_PER_SUPERFRAME >> 2)
#define SLIM_SL_PER_SUPERFRAME (SLIM_CL_PER_SUPERFRAME >> 2)
/* Manager's logical address is set to 0xFF per spec */
#define SLIM_LA_MANAGER 0xFF
#define SLIM_MAX_TIDS 256
/**
* struct slim_framer - Represents SLIMbus framer.
* Every controller may have multiple framers. There is 1 active framer device
* responsible for clocking the bus.
* Manager is responsible for framer hand-over.
* @dev: Driver model representation of the device.
* @e_addr: Enumeration address of the framer.
* @rootfreq: Root Frequency at which the framer can run. This is maximum
* frequency ('clock gear 10') at which the bus can operate.
* @superfreq: Superframes per root frequency. Every frame is 6144 bits.
*/
struct slim_framer {
struct device dev;
struct slim_eaddr e_addr;
int rootfreq;
int superfreq;
};
#define to_slim_framer(d) container_of(d, struct slim_framer, dev)
/**
* struct slim_msg_txn - Message to be sent by the controller.
* This structure has packet header,
* payload and buffer to be filled (if any)
* @rl: Header field. remaining length.
* @mt: Header field. Message type.
* @mc: Header field. LSB is message code for type mt.
* @dt: Header field. Destination type.
* @ec: Element code. Used for elemental access APIs.
* @tid: Transaction ID. Used for messages expecting response.
* (relevant for message-codes involving read operation)
* @la: Logical address of the device this message is going to.
* (Not used when destination type is broadcast.)
* @msg: Elemental access message to be read/written
* @comp: completion if read/write is synchronous, used internally
* for tid based transactions.
*/
struct slim_msg_txn {
u8 rl;
u8 mt;
u8 mc;
u8 dt;
u16 ec;
u8 tid;
u8 la;
struct slim_val_inf *msg;
struct completion *comp;
};
/* Frequently used message transaction structures */
#define DEFINE_SLIM_LDEST_TXN(name, mc, rl, la, msg) \
struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_LOGICALADDR, 0,\
0, la, msg, }
#define DEFINE_SLIM_BCAST_TXN(name, mc, rl, la, msg) \
struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_BROADCAST, 0,\
0, la, msg, }
#define DEFINE_SLIM_EDEST_TXN(name, mc, rl, la, msg) \
struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_ENUMADDR, 0,\
0, la, msg, }
/**
* 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_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 back to SLIM_CLK_ACTIVE
* @SLIM_CLK_PAUSED: SLIMbus controller clock has paused.
*/
enum slim_clk_state {
SLIM_CLK_ACTIVE,
SLIM_CLK_ENTERING_PAUSE,
SLIM_CLK_PAUSED,
};
/**
* struct slim_sched: Framework uses this structure internally for scheduling.
* @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 is entering
* clock pause.
* @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.
*/
struct slim_sched {
enum slim_clk_state clk_state;
struct completion pause_comp;
struct mutex m_reconf;
};
/**
* enum slim_port_direction: SLIMbus port direction
*
* @SLIM_PORT_SINK: SLIMbus port is a sink
* @SLIM_PORT_SOURCE: SLIMbus port is a source
*/
enum slim_port_direction {
SLIM_PORT_SINK = 0,
SLIM_PORT_SOURCE,
};
/**
* enum slim_port_state: SLIMbus Port/Endpoint state machine
* according to SLIMbus Spec 2.0
* @SLIM_PORT_DISCONNECTED: SLIMbus port is disconnected
* entered from Unconfigure/configured state after
* DISCONNECT_PORT or REMOVE_CHANNEL core command
* @SLIM_PORT_UNCONFIGURED: SLIMbus port is in unconfigured state.
* entered from disconnect state after CONNECT_SOURCE/SINK core command
* @SLIM_PORT_CONFIGURED: SLIMbus port is in configured state.
* entered from unconfigured state after DEFINE_CHANNEL, DEFINE_CONTENT
* and ACTIVATE_CHANNEL core commands. Ready for data transmission.
*/
enum slim_port_state {
SLIM_PORT_DISCONNECTED = 0,
SLIM_PORT_UNCONFIGURED,
SLIM_PORT_CONFIGURED,
};
/**
* enum slim_channel_state: SLIMbus channel state machine used by core.
* @SLIM_CH_STATE_DISCONNECTED: SLIMbus channel is disconnected
* @SLIM_CH_STATE_ALLOCATED: SLIMbus channel is allocated
* @SLIM_CH_STATE_ASSOCIATED: SLIMbus channel is associated with port
* @SLIM_CH_STATE_DEFINED: SLIMbus channel parameters are defined
* @SLIM_CH_STATE_CONTENT_DEFINED: SLIMbus channel content is defined
* @SLIM_CH_STATE_ACTIVE: SLIMbus channel is active and ready for data
* @SLIM_CH_STATE_REMOVED: SLIMbus channel is inactive and removed
*/
enum slim_channel_state {
SLIM_CH_STATE_DISCONNECTED = 0,
SLIM_CH_STATE_ALLOCATED,
SLIM_CH_STATE_ASSOCIATED,
SLIM_CH_STATE_DEFINED,
SLIM_CH_STATE_CONTENT_DEFINED,
SLIM_CH_STATE_ACTIVE,
SLIM_CH_STATE_REMOVED,
};
/**
* enum slim_ch_data_fmt: SLIMbus channel data Type identifiers according to
* Table 60 of SLIMbus Spec 1.01.01
* @SLIM_CH_DATA_FMT_NOT_DEFINED: Undefined
* @SLIM_CH_DATA_FMT_LPCM_AUDIO: LPCM audio
* @SLIM_CH_DATA_FMT_IEC61937_COMP_AUDIO: IEC61937 Compressed audio
* @SLIM_CH_DATA_FMT_PACKED_PDM_AUDIO: Packed PDM audio
*/
enum slim_ch_data_fmt {
SLIM_CH_DATA_FMT_NOT_DEFINED = 0,
SLIM_CH_DATA_FMT_LPCM_AUDIO = 1,
SLIM_CH_DATA_FMT_IEC61937_COMP_AUDIO = 2,
SLIM_CH_DATA_FMT_PACKED_PDM_AUDIO = 3,
};
/**
* enum slim_ch_aux_bit_fmt: SLIMbus channel Aux Field format IDs according to
* Table 63 of SLIMbus Spec 2.0
* @SLIM_CH_AUX_FMT_NOT_APPLICABLE: Undefined
* @SLIM_CH_AUX_FMT_ZCUV_TUNNEL_IEC60958: ZCUV for tunneling IEC60958
* @SLIM_CH_AUX_FMT_USER_DEFINED: User defined
*/
enum slim_ch_aux_bit_fmt {
SLIM_CH_AUX_FMT_NOT_APPLICABLE = 0,
SLIM_CH_AUX_FMT_ZCUV_TUNNEL_IEC60958 = 1,
SLIM_CH_AUX_FMT_USER_DEFINED = 0xF,
};
/**
* struct slim_channel - SLIMbus channel, used for state machine
*
* @id: ID of channel
* @prrate: Presense rate of channel from Table 66 of SLIMbus 2.0 Specs
* @seg_dist: segment distribution code from Table 20 of SLIMbus 2.0 Specs
* @data_fmt: Data format of channel.
* @aux_fmt: Aux format for this channel.
* @state: channel state machine
*/
struct slim_channel {
int id;
int prrate;
int seg_dist;
enum slim_ch_data_fmt data_fmt;
enum slim_ch_aux_bit_fmt aux_fmt;
enum slim_channel_state state;
};
/**
* struct slim_port - SLIMbus port
*
* @id: Port id
* @direction: Port direction, Source or Sink.
* @state: state machine of port.
* @ch: channel associated with this port.
*/
struct slim_port {
int id;
enum slim_port_direction direction;
enum slim_port_state state;
struct slim_channel ch;
};
/**
* enum slim_transport_protocol: SLIMbus Transport protocol list from
* Table 47 of SLIMbus 2.0 specs.
* @SLIM_PROTO_ISO: Isochronous Protocol, no flow control as data rate match
* channel rate flow control embedded in the data.
* @SLIM_PROTO_PUSH: Pushed Protocol, includes flow control, Used to carry
* data whose rate is equal to, or lower than the channel rate.
* @SLIM_PROTO_PULL: Pulled Protocol, similar usage as pushed protocol
* but pull is a unicast.
* @SLIM_PROTO_LOCKED: Locked Protocol
* @SLIM_PROTO_ASYNC_SMPLX: Asynchronous Protocol-Simplex
* @SLIM_PROTO_ASYNC_HALF_DUP: Asynchronous Protocol-Half-duplex
* @SLIM_PROTO_EXT_SMPLX: Extended Asynchronous Protocol-Simplex
* @SLIM_PROTO_EXT_HALF_DUP: Extended Asynchronous Protocol-Half-duplex
*/
enum slim_transport_protocol {
SLIM_PROTO_ISO = 0,
SLIM_PROTO_PUSH,
SLIM_PROTO_PULL,
SLIM_PROTO_LOCKED,
SLIM_PROTO_ASYNC_SMPLX,
SLIM_PROTO_ASYNC_HALF_DUP,
SLIM_PROTO_EXT_SMPLX,
SLIM_PROTO_EXT_HALF_DUP,
};
/**
* struct slim_stream_runtime - SLIMbus stream runtime instance
*
* @name: Name of the stream
* @dev: SLIM Device instance associated with this stream
* @direction: direction of stream
* @prot: Transport protocol used in this stream
* @rate: Data rate of samples *
* @bps: bits per sample
* @ratem: rate multipler which is super frame rate/data rate
* @num_ports: number of ports
* @ports: pointer to instance of ports
* @node: list head for stream associated with slim device.
*/
struct slim_stream_runtime {
const char *name;
struct slim_device *dev;
int direction;
enum slim_transport_protocol prot;
unsigned int rate;
unsigned int bps;
unsigned int ratem;
int num_ports;
struct slim_port *ports;
struct list_head node;
};
/**
* struct slim_controller - Controls every instance of SLIMbus
* (similar to 'master' on SPI)
* @dev: Device interface to this driver
* @id: Board-specific number identifier for this controller/bus
* @name: Name for this controller
* @min_cg: Minimum clock gear supported by this controller (default value: 1)
* @max_cg: Maximum clock gear supported by this controller (default value: 10)
* @clkgear: Current clock gear in which this bus is running
* @laddr_ida: logical address id allocator
* @a_framer: Active framer which is clocking the bus managed by this controller
* @lock: Mutex protecting controller data structures
* @devices: Slim device list
* @tid_idr: tid id allocator
* @txn_lock: Lock to protect table of transactions
* @sched: scheduler structure used by the controller
* @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.
* @get_laddr: It is possible that controller needs to set fixed logical
* address table and get_laddr can be used in that case so that controller
* can do this assignment. Use case is when the master is on the remote
* processor side, who is resposible for allocating laddr.
* @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.
* @enable_stream: This function pointer implements controller-specific procedure
* to enable a stream.
* @disable_stream: This function pointer implements controller-specific procedure
* to disable stream.
*
* 'Manager device' is responsible for device management, bandwidth
* allocation, channel setup, and port associations per channel.
* Device management means Logical address assignment/removal based on
* enumeration (report-present, report-absent) of a device.
* Bandwidth allocation is done dynamically by the manager based on active
* channels on the bus, message-bandwidth requests made by SLIMbus devices.
* Based on current bandwidth usage, manager chooses a frequency to run
* the bus at (in steps of 'clock-gear', 1 through 10, each clock gear
* representing twice the frequency than the previous gear).
* Manager is also responsible for entering (and exiting) low-power-mode
* (known as 'clock pause').
* Manager can do handover of framer if there are multiple framers on the
* bus and a certain usecase warrants using certain framer to avoid keeping
* previous framer being powered-on.
*
* Controller here performs duties of the manager device, and 'interface
* device'. Interface device is responsible for monitoring the bus and
* reporting information such as loss-of-synchronization, data
* slot-collision.
*/
struct slim_controller {
struct device *dev;
unsigned int id;
char name[SLIMBUS_NAME_SIZE];
int min_cg;
int max_cg;
int clkgear;
struct ida laddr_ida;
struct slim_framer *a_framer;
struct mutex lock;
struct list_head devices;
struct idr tid_idr;
spinlock_t txn_lock;
struct slim_sched sched;
int (*xfer_msg)(struct slim_controller *ctrl,
struct slim_msg_txn *tx);
int (*set_laddr)(struct slim_controller *ctrl,
struct slim_eaddr *ea, u8 laddr);
int (*get_laddr)(struct slim_controller *ctrl,
struct slim_eaddr *ea, u8 *laddr);
int (*enable_stream)(struct slim_stream_runtime *rt);
int (*disable_stream)(struct slim_stream_runtime *rt);
int (*wakeup)(struct slim_controller *ctrl);
};
int slim_device_report_present(struct slim_controller *ctrl,
struct slim_eaddr *e_addr, u8 *laddr);
void slim_report_absent(struct slim_device *sbdev);
int slim_register_controller(struct slim_controller *ctrl);
int slim_unregister_controller(struct slim_controller *ctrl);
void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 l);
int slim_do_transfer(struct slim_controller *ctrl, struct slim_msg_txn *txn);
int slim_ctrl_clk_pause(struct slim_controller *ctrl, bool wakeup, u8 restart);
int slim_alloc_txn_tid(struct slim_controller *ctrl, struct slim_msg_txn *txn);
void slim_free_txn_tid(struct slim_controller *ctrl, struct slim_msg_txn *txn);
static inline bool slim_tid_txn(u8 mt, u8 mc)
{
return (mt == SLIM_MSG_MT_CORE &&
(mc == SLIM_MSG_MC_REQUEST_INFORMATION ||
mc == SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION ||
mc == SLIM_MSG_MC_REQUEST_VALUE ||
mc == SLIM_MSG_MC_REQUEST_CHANGE_VALUE));
}
static inline bool slim_ec_txn(u8 mt, u8 mc)
{
return (mt == SLIM_MSG_MT_CORE &&
((mc >= SLIM_MSG_MC_REQUEST_INFORMATION &&
mc <= SLIM_MSG_MC_REPORT_INFORMATION) ||
(mc >= SLIM_MSG_MC_REQUEST_VALUE &&
mc <= SLIM_MSG_MC_CHANGE_VALUE)));
}
#endif /* _LINUX_SLIMBUS_H */
+492
View File
@@ -0,0 +1,492 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018, Linaro Limited
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/slimbus.h>
#include <uapi/sound/asound.h>
#include "slimbus.h"
/**
* struct segdist_code - Segment Distributions code from
* Table 20 of SLIMbus Specs Version 2.0
*
* @ratem: Channel Rate Multipler(Segments per Superframe)
* @seg_interval: Number of slots between the first Slot of Segment
* and the first slot of the next consecutive Segment.
* @segdist_code: Segment Distribution Code SD[11:0]
* @seg_offset_mask: Segment offset mask in SD[11:0]
*/
struct segdist_code {
int ratem;
int seg_interval;
int segdist_code;
u32 seg_offset_mask;
};
/* segdist_codes - List of all possible Segment Distribution codes. */
static const struct segdist_code segdist_codes[] = {
{1, 1536, 0x200, 0xdff},
{2, 768, 0x100, 0xcff},
{4, 384, 0x080, 0xc7f},
{8, 192, 0x040, 0xc3f},
{16, 96, 0x020, 0xc1f},
{32, 48, 0x010, 0xc0f},
{64, 24, 0x008, 0xc07},
{128, 12, 0x004, 0xc03},
{256, 6, 0x002, 0xc01},
{512, 3, 0x001, 0xc00},
{3, 512, 0xe00, 0x1ff},
{6, 256, 0xd00, 0x0ff},
{12, 128, 0xc80, 0x07f},
{24, 64, 0xc40, 0x03f},
{48, 32, 0xc20, 0x01f},
{96, 16, 0xc10, 0x00f},
{192, 8, 0xc08, 0x007},
{364, 4, 0xc04, 0x003},
{768, 2, 0xc02, 0x001},
};
/*
* Presence Rate table for all Natural Frequencies
* The Presence rate of a constant bitrate stream is mean flow rate of the
* stream expressed in occupied Segments of that Data Channel per second.
* Table 66 from SLIMbus 2.0 Specs
*
* Index of the table corresponds to Presence rate code for the respective rate
* in the table.
*/
static const int slim_presence_rate_table[] = {
0, /* Not Indicated */
12000,
24000,
48000,
96000,
192000,
384000,
768000,
0, /* Reserved */
11025,
22050,
44100,
88200,
176400,
352800,
705600,
4000,
8000,
16000,
32000,
64000,
128000,
256000,
512000,
};
/**
* slim_stream_allocate() - Allocate a new SLIMbus Stream
* @dev:Slim device to be associated with
* @name: name of the stream
*
* This is very first call for SLIMbus streaming, this API will allocate
* a new SLIMbus stream and return a valid stream runtime pointer for client
* to use it in subsequent stream apis. state of stream is set to ALLOCATED
*
* Return: valid pointer on success and error code on failure.
* From ASoC DPCM framework, this state is linked to startup() operation.
*/
struct slim_stream_runtime *slim_stream_allocate(struct slim_device *dev,
const char *name)
{
struct slim_stream_runtime *rt;
rt = kzalloc_obj(*rt);
if (!rt)
return ERR_PTR(-ENOMEM);
rt->name = kasprintf(GFP_KERNEL, "slim-%s", name);
if (!rt->name) {
kfree(rt);
return ERR_PTR(-ENOMEM);
}
rt->dev = dev;
spin_lock(&dev->stream_list_lock);
list_add_tail(&rt->node, &dev->stream_list);
spin_unlock(&dev->stream_list_lock);
return rt;
}
EXPORT_SYMBOL_GPL(slim_stream_allocate);
static int slim_connect_port_channel(struct slim_stream_runtime *stream,
struct slim_port *port)
{
struct slim_device *sdev = stream->dev;
u8 wbuf[2];
struct slim_val_inf msg = {0, 2, NULL, wbuf, NULL};
u8 mc = SLIM_MSG_MC_CONNECT_SOURCE;
DEFINE_SLIM_LDEST_TXN(txn, mc, 6, stream->dev->laddr, &msg);
if (port->direction == SLIM_PORT_SINK)
txn.mc = SLIM_MSG_MC_CONNECT_SINK;
wbuf[0] = port->id;
wbuf[1] = port->ch.id;
port->ch.state = SLIM_CH_STATE_ASSOCIATED;
port->state = SLIM_PORT_UNCONFIGURED;
return slim_do_transfer(sdev->ctrl, &txn);
}
static int slim_disconnect_port(struct slim_stream_runtime *stream,
struct slim_port *port)
{
struct slim_device *sdev = stream->dev;
u8 wbuf[1];
struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL};
u8 mc = SLIM_MSG_MC_DISCONNECT_PORT;
DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg);
wbuf[0] = port->id;
port->ch.state = SLIM_CH_STATE_DISCONNECTED;
port->state = SLIM_PORT_DISCONNECTED;
return slim_do_transfer(sdev->ctrl, &txn);
}
static int slim_deactivate_remove_channel(struct slim_stream_runtime *stream,
struct slim_port *port)
{
struct slim_device *sdev = stream->dev;
u8 wbuf[1];
struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL};
u8 mc = SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL;
DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg);
int ret;
wbuf[0] = port->ch.id;
ret = slim_do_transfer(sdev->ctrl, &txn);
if (ret)
return ret;
txn.mc = SLIM_MSG_MC_NEXT_REMOVE_CHANNEL;
port->ch.state = SLIM_CH_STATE_REMOVED;
return slim_do_transfer(sdev->ctrl, &txn);
}
static int slim_get_prate_code(int rate)
{
int i;
for (i = 0; i < ARRAY_SIZE(slim_presence_rate_table); i++) {
if (rate == slim_presence_rate_table[i])
return i;
}
return -EINVAL;
}
/**
* slim_stream_prepare() - Prepare a SLIMbus Stream
*
* @rt: instance of slim stream runtime to configure
* @cfg: new configuration for the stream
*
* This API will configure SLIMbus stream with config parameters from cfg.
* return zero on success and error code on failure. From ASoC DPCM framework,
* this state is linked to hw_params() operation.
*/
int slim_stream_prepare(struct slim_stream_runtime *rt,
struct slim_stream_config *cfg)
{
struct slim_controller *ctrl = rt->dev->ctrl;
struct slim_port *port;
int num_ports, i, port_id, prrate;
if (rt->ports) {
dev_err(&rt->dev->dev, "Stream already Prepared\n");
return -EINVAL;
}
num_ports = hweight32(cfg->port_mask);
rt->ports = kzalloc_objs(*port, num_ports);
if (!rt->ports)
return -ENOMEM;
rt->num_ports = num_ports;
rt->rate = cfg->rate;
rt->bps = cfg->bps;
rt->direction = cfg->direction;
prrate = slim_get_prate_code(cfg->rate);
if (prrate < 0) {
dev_err(&rt->dev->dev, "Cannot get presence rate for rate %d Hz\n",
cfg->rate);
return prrate;
}
if (cfg->rate % ctrl->a_framer->superfreq) {
/*
* data rate not exactly multiple of super frame,
* use PUSH/PULL protocol
*/
if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK)
rt->prot = SLIM_PROTO_PUSH;
else
rt->prot = SLIM_PROTO_PULL;
} else {
rt->prot = SLIM_PROTO_ISO;
}
rt->ratem = cfg->rate/ctrl->a_framer->superfreq;
i = 0;
for_each_set_bit(port_id, &cfg->port_mask, SLIM_DEVICE_MAX_PORTS) {
port = &rt->ports[i];
port->state = SLIM_PORT_DISCONNECTED;
port->id = port_id;
port->ch.prrate = prrate;
port->ch.id = cfg->chs[i];
port->ch.data_fmt = SLIM_CH_DATA_FMT_NOT_DEFINED;
port->ch.aux_fmt = SLIM_CH_AUX_FMT_NOT_APPLICABLE;
port->ch.state = SLIM_CH_STATE_ALLOCATED;
if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK)
port->direction = SLIM_PORT_SINK;
else
port->direction = SLIM_PORT_SOURCE;
slim_connect_port_channel(rt, port);
i++;
}
return 0;
}
EXPORT_SYMBOL_GPL(slim_stream_prepare);
static int slim_define_channel_content(struct slim_stream_runtime *stream,
struct slim_port *port)
{
struct slim_device *sdev = stream->dev;
u8 wbuf[4];
struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL};
u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CONTENT;
DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg);
wbuf[0] = port->ch.id;
wbuf[1] = port->ch.prrate;
/* Frequency Locked for ISO Protocol */
if (stream->prot != SLIM_PROTO_ISO)
wbuf[1] |= SLIM_CHANNEL_CONTENT_FL;
wbuf[2] = port->ch.data_fmt | (port->ch.aux_fmt << 4);
wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS;
port->ch.state = SLIM_CH_STATE_CONTENT_DEFINED;
return slim_do_transfer(sdev->ctrl, &txn);
}
static int slim_get_segdist_code(int ratem)
{
int i;
for (i = 0; i < ARRAY_SIZE(segdist_codes); i++) {
if (segdist_codes[i].ratem == ratem)
return segdist_codes[i].segdist_code;
}
return -EINVAL;
}
static int slim_define_channel(struct slim_stream_runtime *stream,
struct slim_port *port)
{
struct slim_device *sdev = stream->dev;
u8 wbuf[4];
struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL};
u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CHANNEL;
DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg);
port->ch.seg_dist = slim_get_segdist_code(stream->ratem);
wbuf[0] = port->ch.id;
wbuf[1] = port->ch.seg_dist & 0xFF;
wbuf[2] = (stream->prot << 4) | ((port->ch.seg_dist & 0xF00) >> 8);
if (stream->prot == SLIM_PROTO_ISO)
wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS;
else
wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS + 1;
port->ch.state = SLIM_CH_STATE_DEFINED;
return slim_do_transfer(sdev->ctrl, &txn);
}
static int slim_activate_channel(struct slim_stream_runtime *stream,
struct slim_port *port)
{
struct slim_device *sdev = stream->dev;
u8 wbuf[1];
struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL};
u8 mc = SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL;
DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg);
txn.msg->num_bytes = 1;
txn.msg->wbuf = wbuf;
wbuf[0] = port->ch.id;
port->ch.state = SLIM_CH_STATE_ACTIVE;
return slim_do_transfer(sdev->ctrl, &txn);
}
/**
* slim_stream_enable() - Enable a prepared SLIMbus Stream
*
* @stream: instance of slim stream runtime to enable
*
* This API will enable all the ports and channels associated with
* SLIMbus stream
*
* Return: zero on success and error code on failure. From ASoC DPCM framework,
* this state is linked to trigger() start operation.
*/
int slim_stream_enable(struct slim_stream_runtime *stream)
{
DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION,
3, SLIM_LA_MANAGER, NULL);
struct slim_controller *ctrl = stream->dev->ctrl;
int ret, i;
if (ctrl->enable_stream) {
ret = ctrl->enable_stream(stream);
if (ret)
return ret;
for (i = 0; i < stream->num_ports; i++)
stream->ports[i].ch.state = SLIM_CH_STATE_ACTIVE;
return ret;
}
ret = slim_do_transfer(ctrl, &txn);
if (ret)
return ret;
/* define channels first before activating them */
for (i = 0; i < stream->num_ports; i++) {
struct slim_port *port = &stream->ports[i];
slim_define_channel(stream, port);
slim_define_channel_content(stream, port);
}
for (i = 0; i < stream->num_ports; i++) {
struct slim_port *port = &stream->ports[i];
slim_activate_channel(stream, port);
port->state = SLIM_PORT_CONFIGURED;
}
txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW;
return slim_do_transfer(ctrl, &txn);
}
EXPORT_SYMBOL_GPL(slim_stream_enable);
/**
* slim_stream_disable() - Disable a SLIMbus Stream
*
* @stream: instance of slim stream runtime to disable
*
* This API will disable all the ports and channels associated with
* SLIMbus stream
*
* Return: zero on success and error code on failure. From ASoC DPCM framework,
* this state is linked to trigger() pause operation.
*/
int slim_stream_disable(struct slim_stream_runtime *stream)
{
DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION,
3, SLIM_LA_MANAGER, NULL);
struct slim_controller *ctrl = stream->dev->ctrl;
int ret, i;
if (!stream->ports || !stream->num_ports)
return -EINVAL;
if (ctrl->disable_stream)
ctrl->disable_stream(stream);
ret = slim_do_transfer(ctrl, &txn);
if (ret)
return ret;
for (i = 0; i < stream->num_ports; i++)
slim_deactivate_remove_channel(stream, &stream->ports[i]);
txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW;
return slim_do_transfer(ctrl, &txn);
}
EXPORT_SYMBOL_GPL(slim_stream_disable);
/**
* slim_stream_unprepare() - Un-prepare a SLIMbus Stream
*
* @stream: instance of slim stream runtime to unprepare
*
* This API will un allocate all the ports and channels associated with
* SLIMbus stream
*
* Return: zero on success and error code on failure. From ASoC DPCM framework,
* this state is linked to trigger() stop operation.
*/
int slim_stream_unprepare(struct slim_stream_runtime *stream)
{
int i;
if (!stream->ports || !stream->num_ports)
return -EINVAL;
for (i = 0; i < stream->num_ports; i++)
slim_disconnect_port(stream, &stream->ports[i]);
kfree(stream->ports);
stream->ports = NULL;
stream->num_ports = 0;
return 0;
}
EXPORT_SYMBOL_GPL(slim_stream_unprepare);
/**
* slim_stream_free() - Free a SLIMbus Stream
*
* @stream: instance of slim stream runtime to free
*
* This API will un allocate all the memory associated with
* slim stream runtime, user is not allowed to make an dereference
* to stream after this call.
*
* Return: zero on success and error code on failure. From ASoC DPCM framework,
* this state is linked to shutdown() operation.
*/
int slim_stream_free(struct slim_stream_runtime *stream)
{
struct slim_device *sdev = stream->dev;
spin_lock(&sdev->stream_list_lock);
list_del(&stream->node);
spin_unlock(&sdev->stream_list_lock);
kfree(stream->name);
kfree(stream);
return 0;
}
EXPORT_SYMBOL_GPL(slim_stream_free);