diff options
Diffstat (limited to 'drivers/slimbus/stream.c')
| -rw-r--r-- | drivers/slimbus/stream.c | 477 | 
1 files changed, 477 insertions, 0 deletions
| diff --git a/drivers/slimbus/stream.c b/drivers/slimbus/stream.c new file mode 100644 index 000000000000..2fa05324ed07 --- /dev/null +++ b/drivers/slimbus/stream.c @@ -0,0 +1,477 @@ +// 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] + * @segdist_codes: List of all possible Segmet Distribution codes. + */ +static const struct segdist_code { +	int ratem; +	int seg_interval; +	int segdist_code; +	u32 seg_offset_mask; + +} 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 */ +	110250, +	220500, +	441000, +	882000, +	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(sizeof(*rt), GFP_KERNEL); +	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; + +	if (rt->ports) { +		dev_err(&rt->dev->dev, "Stream already Prepared\n"); +		return -EINVAL; +	} + +	num_ports = hweight32(cfg->port_mask); +	rt->ports = kcalloc(num_ports, sizeof(*port), GFP_KERNEL); +	if (!rt->ports) +		return -ENOMEM; + +	rt->num_ports = num_ports; +	rt->rate = cfg->rate; +	rt->bps = cfg->bps; +	rt->direction = cfg->direction; + +	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 = slim_get_prate_code(cfg->rate); +		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 (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; + +	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); |