|  | // 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); |