478 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			478 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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);
 |