File Index
Files and directories
Sign in
FILE

q6adm.c

File preview with syntax highlighting.

linux-6.19.14/sound/soc/qcom/qdsp6/q6adm.c C
  1// SPDX-License-Identifier: GPL-2.0
  2// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
  3// Copyright (c) 2018, Linaro Limited
  4
  5#include <linux/device.h>
  6#include <linux/jiffies.h>
  7#include <linux/kernel.h>
  8#include <linux/kref.h>
  9#include <linux/module.h>
 10#include <linux/of.h>
 11#include <linux/of_platform.h>
 12#include <linux/platform_device.h>
 13#include <linux/sched.h>
 14#include <linux/slab.h>
 15#include <linux/soc/qcom/apr.h>
 16#include <linux/wait.h>
 17#include <sound/asound.h>
 18#include "q6adm.h"
 19#include "q6afe.h"
 20#include "q6core.h"
 21#include "q6dsp-common.h"
 22#include "q6dsp-errno.h"
 23
 24#define ADM_CMD_DEVICE_OPEN_V5		0x00010326
 25#define ADM_CMDRSP_DEVICE_OPEN_V5	0x00010329
 26#define ADM_CMD_DEVICE_CLOSE_V5		0x00010327
 27#define ADM_CMD_MATRIX_MAP_ROUTINGS_V5	0x00010325
 28
 29#define TIMEOUT_MS 1000
 30#define RESET_COPP_ID 99
 31#define INVALID_COPP_ID 0xFF
 32/* Definition for a legacy device session. */
 33#define ADM_LEGACY_DEVICE_SESSION	0
 34#define ADM_MATRIX_ID_AUDIO_RX		0
 35#define ADM_MATRIX_ID_AUDIO_TX		1
 36
 37struct q6copp {
 38	int afe_port;
 39	int copp_idx;
 40	int id;
 41	int topology;
 42	int mode;
 43	int rate;
 44	int bit_width;
 45	int channels;
 46	int app_type;
 47	int acdb_id;
 48
 49	struct aprv2_ibasic_rsp_result_t result;
 50	struct kref refcount;
 51	wait_queue_head_t wait;
 52	struct list_head node;
 53	struct q6adm *adm;
 54};
 55
 56struct q6adm {
 57	struct apr_device *apr;
 58	struct device *dev;
 59	struct q6core_svc_api_info ainfo;
 60	unsigned long copp_bitmap[AFE_MAX_PORTS];
 61	struct list_head copps_list;
 62	spinlock_t copps_list_lock;
 63	struct aprv2_ibasic_rsp_result_t result;
 64	struct mutex lock;
 65	wait_queue_head_t matrix_map_wait;
 66};
 67
 68struct q6adm_cmd_device_open_v5 {
 69	u16 flags;
 70	u16 mode_of_operation;
 71	u16 endpoint_id_1;
 72	u16 endpoint_id_2;
 73	u32 topology_id;
 74	u16 dev_num_channel;
 75	u16 bit_width;
 76	u32 sample_rate;
 77	u8 dev_channel_mapping[8];
 78} __packed;
 79
 80struct q6adm_cmd_matrix_map_routings_v5 {
 81	u32 matrix_id;
 82	u32 num_sessions;
 83} __packed;
 84
 85struct q6adm_session_map_node_v5 {
 86	u16 session_id;
 87	u16 num_copps;
 88} __packed;
 89
 90static struct q6copp *q6adm_find_copp(struct q6adm *adm, int port_idx,
 91				  int copp_idx)
 92{
 93	struct q6copp *c;
 94	struct q6copp *ret = NULL;
 95	unsigned long flags;
 96
 97	spin_lock_irqsave(&adm->copps_list_lock, flags);
 98	list_for_each_entry(c, &adm->copps_list, node) {
 99		if ((port_idx == c->afe_port) && (copp_idx == c->copp_idx)) {
100			ret = c;
101			kref_get(&c->refcount);
102			break;
103		}
104	}
105
106	spin_unlock_irqrestore(&adm->copps_list_lock, flags);
107
108	return ret;
109
110}
111
112static int q6adm_apr_send_copp_pkt(struct q6adm *adm, struct q6copp *copp,
113				   struct apr_pkt *pkt, uint32_t rsp_opcode)
114{
115	struct device *dev = adm->dev;
116	uint32_t opcode = pkt->hdr.opcode;
117	int ret;
118
119	mutex_lock(&adm->lock);
120	copp->result.opcode = 0;
121	copp->result.status = 0;
122	ret = apr_send_pkt(adm->apr, pkt);
123	if (ret < 0) {
124		dev_err(dev, "Failed to send APR packet\n");
125		ret = -EINVAL;
126		goto err;
127	}
128
129	/* Wait for the callback with copp id */
130	if (rsp_opcode)
131		ret = wait_event_timeout(copp->wait,
132					 (copp->result.opcode == opcode) ||
133					 (copp->result.opcode == rsp_opcode),
134					 msecs_to_jiffies(TIMEOUT_MS));
135	else
136		ret = wait_event_timeout(copp->wait,
137					 (copp->result.opcode == opcode),
138					 msecs_to_jiffies(TIMEOUT_MS));
139
140	if (!ret) {
141		dev_err(dev, "ADM copp cmd timedout\n");
142		ret = -ETIMEDOUT;
143	} else if (copp->result.status > 0) {
144		dev_err(dev, "DSP returned error[%d]\n",
145			copp->result.status);
146		ret = -EINVAL;
147	}
148
149err:
150	mutex_unlock(&adm->lock);
151	return ret;
152}
153
154static int q6adm_device_close(struct q6adm *adm, struct q6copp *copp,
155			      int port_id, int copp_idx)
156{
157	struct apr_pkt close;
158
159	close.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
160					APR_HDR_LEN(APR_HDR_SIZE),
161					APR_PKT_VER);
162	close.hdr.pkt_size = sizeof(close);
163	close.hdr.src_port = port_id;
164	close.hdr.dest_port = copp->id;
165	close.hdr.token = port_id << 16 | copp_idx;
166	close.hdr.opcode = ADM_CMD_DEVICE_CLOSE_V5;
167
168	return q6adm_apr_send_copp_pkt(adm, copp, &close, 0);
169}
170
171static void q6adm_free_copp(struct kref *ref)
172{
173	struct q6copp *c = container_of(ref, struct q6copp, refcount);
174	struct q6adm *adm = c->adm;
175	unsigned long flags;
176	int ret;
177
178	ret = q6adm_device_close(adm, c, c->afe_port, c->copp_idx);
179	if (ret < 0)
180		dev_err(adm->dev, "Failed to close copp %d\n", ret);
181
182	spin_lock_irqsave(&adm->copps_list_lock, flags);
183	clear_bit(c->copp_idx, &adm->copp_bitmap[c->afe_port]);
184	list_del(&c->node);
185	spin_unlock_irqrestore(&adm->copps_list_lock, flags);
186	kfree(c);
187}
188
189static int q6adm_callback(struct apr_device *adev, struct apr_resp_pkt *data)
190{
191	struct aprv2_ibasic_rsp_result_t *result = data->payload;
192	int port_idx, copp_idx;
193	struct apr_hdr *hdr = &data->hdr;
194	struct q6copp *copp;
195	struct q6adm *adm = dev_get_drvdata(&adev->dev);
196
197	if (!data->payload_size)
198		return 0;
199
200	copp_idx = (hdr->token) & 0XFF;
201	port_idx = ((hdr->token) >> 16) & 0xFF;
202	if (port_idx < 0 || port_idx >= AFE_MAX_PORTS) {
203		dev_err(&adev->dev, "Invalid port idx %d token %d\n",
204		       port_idx, hdr->token);
205		return 0;
206	}
207	if (copp_idx < 0 || copp_idx >= MAX_COPPS_PER_PORT) {
208		dev_err(&adev->dev, "Invalid copp idx %d token %d\n",
209			copp_idx, hdr->token);
210		return 0;
211	}
212
213	switch (hdr->opcode) {
214	case APR_BASIC_RSP_RESULT: {
215		if (result->status != 0) {
216			dev_err(&adev->dev, "cmd = 0x%x return error = 0x%x\n",
217				result->opcode, result->status);
218		}
219		switch (result->opcode) {
220		case ADM_CMD_DEVICE_OPEN_V5:
221		case ADM_CMD_DEVICE_CLOSE_V5:
222			list_for_each_entry(copp, &adm->copps_list, node) {
223				if ((port_idx == copp->afe_port) && (copp_idx == copp->copp_idx)) {
224					copp->result = *result;
225					wake_up(&copp->wait);
226					break;
227				}
228			}
229			break;
230		case ADM_CMD_MATRIX_MAP_ROUTINGS_V5:
231			adm->result = *result;
232			wake_up(&adm->matrix_map_wait);
233			break;
234
235		default:
236			dev_err(&adev->dev, "Unknown Cmd: 0x%x\n",
237				result->opcode);
238			break;
239		}
240		return 0;
241	}
242	case ADM_CMDRSP_DEVICE_OPEN_V5: {
243		struct adm_cmd_rsp_device_open_v5 {
244			u32 status;
245			u16 copp_id;
246			u16 reserved;
247		} __packed *open = data->payload;
248
249		copp = q6adm_find_copp(adm, port_idx, copp_idx);
250		if (!copp)
251			return 0;
252
253		if (open->copp_id == INVALID_COPP_ID) {
254			dev_err(&adev->dev, "Invalid coppid rxed %d\n",
255				open->copp_id);
256			copp->result.status = ADSP_EBADPARAM;
257			wake_up(&copp->wait);
258			kref_put(&copp->refcount, q6adm_free_copp);
259			break;
260		}
261		copp->result.opcode = hdr->opcode;
262		copp->id = open->copp_id;
263		wake_up(&copp->wait);
264		kref_put(&copp->refcount, q6adm_free_copp);
265	}
266	break;
267	default:
268		dev_err(&adev->dev, "Unknown cmd:0x%x\n",
269		       hdr->opcode);
270		break;
271	}
272
273	return 0;
274}
275
276static struct q6copp *q6adm_alloc_copp(struct q6adm *adm, int port_idx)
277{
278	struct q6copp *c;
279	int idx;
280
281	idx = find_first_zero_bit(&adm->copp_bitmap[port_idx],
282				  MAX_COPPS_PER_PORT);
283
284	if (idx >= MAX_COPPS_PER_PORT)
285		return ERR_PTR(-EBUSY);
286
287	c = kzalloc(sizeof(*c), GFP_ATOMIC);
288	if (!c)
289		return ERR_PTR(-ENOMEM);
290
291	set_bit(idx, &adm->copp_bitmap[port_idx]);
292	c->copp_idx = idx;
293	c->afe_port = port_idx;
294	c->adm = adm;
295
296	init_waitqueue_head(&c->wait);
297
298	return c;
299}
300
301static struct q6copp *q6adm_find_matching_copp(struct q6adm *adm,
302					       int port_id, int topology,
303					       int mode, int rate,
304					       int channel_mode, int bit_width,
305					       int app_type)
306{
307	struct q6copp *c;
308	struct q6copp *ret = NULL;
309	unsigned long flags;
310
311	spin_lock_irqsave(&adm->copps_list_lock, flags);
312
313	list_for_each_entry(c, &adm->copps_list, node) {
314		if ((port_id == c->afe_port) && (topology == c->topology) &&
315		    (mode == c->mode) && (rate == c->rate) &&
316		    (bit_width == c->bit_width) && (app_type == c->app_type)) {
317			ret = c;
318			kref_get(&c->refcount);
319		}
320	}
321	spin_unlock_irqrestore(&adm->copps_list_lock, flags);
322
323	return ret;
324}
325
326static int q6adm_device_open(struct q6adm *adm, struct q6copp *copp,
327			     int port_id, int path, int topology,
328			     int channel_mode, int bit_width, int rate)
329{
330	struct q6adm_cmd_device_open_v5 *open;
331	int afe_port = q6afe_get_port_id(port_id);
332	struct apr_pkt *pkt;
333	int ret, pkt_size = APR_HDR_SIZE + sizeof(*open);
334	void *p __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
335	if (!p)
336		return -ENOMEM;
337
338	pkt = p;
339	open = p + APR_HDR_SIZE;
340	pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
341					   APR_HDR_LEN(APR_HDR_SIZE),
342					   APR_PKT_VER);
343	pkt->hdr.pkt_size = pkt_size;
344	pkt->hdr.src_port = afe_port;
345	pkt->hdr.dest_port = afe_port;
346	pkt->hdr.token = port_id << 16 | copp->copp_idx;
347	pkt->hdr.opcode = ADM_CMD_DEVICE_OPEN_V5;
348	open->flags = ADM_LEGACY_DEVICE_SESSION;
349	open->mode_of_operation = path;
350	open->endpoint_id_1 = afe_port;
351	open->topology_id = topology;
352	open->dev_num_channel = channel_mode & 0x00FF;
353	open->bit_width = bit_width;
354	open->sample_rate = rate;
355
356	ret = q6dsp_map_channels(&open->dev_channel_mapping[0],
357				 channel_mode);
358	if (ret)
359		return ret;
360
361	return q6adm_apr_send_copp_pkt(adm, copp, pkt, ADM_CMDRSP_DEVICE_OPEN_V5);
362}
363
364/**
365 * q6adm_open() - open adm and grab a free copp
366 *
367 * @dev: Pointer to adm child device.
368 * @port_id: port id
369 * @path: playback or capture path.
370 * @rate: rate at which copp is required.
371 * @channel_mode: channel mode
372 * @topology: adm topology id
373 * @perf_mode: performace mode.
374 * @bit_width: audio sample bit width
375 * @app_type: Application type.
376 * @acdb_id: ACDB id
377 *
378 * Return: Will be an negative on error or a valid copp pointer on success.
379 */
380struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate,
381	       int channel_mode, int topology, int perf_mode,
382	       uint16_t bit_width, int app_type, int acdb_id)
383{
384	struct q6adm *adm = dev_get_drvdata(dev->parent);
385	struct q6copp *copp;
386	unsigned long flags;
387	int ret = 0;
388
389	if (port_id < 0) {
390		dev_err(dev, "Invalid port_id %d\n", port_id);
391		return ERR_PTR(-EINVAL);
392	}
393
394	copp = q6adm_find_matching_copp(adm, port_id, topology, perf_mode,
395				      rate, channel_mode, bit_width, app_type);
396	if (copp) {
397		dev_err(dev, "Found Matching Copp 0x%x\n", copp->copp_idx);
398		return copp;
399	}
400
401	spin_lock_irqsave(&adm->copps_list_lock, flags);
402	copp = q6adm_alloc_copp(adm, port_id);
403	if (IS_ERR(copp)) {
404		spin_unlock_irqrestore(&adm->copps_list_lock, flags);
405		return ERR_CAST(copp);
406	}
407
408	list_add_tail(&copp->node, &adm->copps_list);
409	spin_unlock_irqrestore(&adm->copps_list_lock, flags);
410
411	kref_init(&copp->refcount);
412	copp->topology = topology;
413	copp->mode = perf_mode;
414	copp->rate = rate;
415	copp->channels = channel_mode;
416	copp->bit_width = bit_width;
417	copp->app_type = app_type;
418
419	ret = q6adm_device_open(adm, copp, port_id, path, topology,
420				channel_mode, bit_width, rate);
421	if (ret < 0) {
422		kref_put(&copp->refcount, q6adm_free_copp);
423		return ERR_PTR(ret);
424	}
425
426	return copp;
427}
428EXPORT_SYMBOL_GPL(q6adm_open);
429
430/**
431 * q6adm_get_copp_id() - get copp index
432 *
433 * @copp: Pointer to valid copp
434 *
435 * Return: Will be an negative on error or a valid copp index on success.
436 **/
437int q6adm_get_copp_id(struct q6copp *copp)
438{
439	if (!copp)
440		return -EINVAL;
441
442	return copp->copp_idx;
443}
444EXPORT_SYMBOL_GPL(q6adm_get_copp_id);
445
446/**
447 * q6adm_matrix_map() - Map asm streams and afe ports using payload
448 *
449 * @dev: Pointer to adm child device.
450 * @path: playback or capture path.
451 * @payload_map: map between session id and afe ports.
452 * @perf_mode: Performace mode.
453 *
454 * Return: Will be an negative on error or a zero on success.
455 */
456int q6adm_matrix_map(struct device *dev, int path,
457		     struct route_payload payload_map, int perf_mode)
458{
459	struct q6adm *adm = dev_get_drvdata(dev->parent);
460	struct q6adm_cmd_matrix_map_routings_v5 *route;
461	struct q6adm_session_map_node_v5 *node;
462	struct apr_pkt *pkt;
463	uint16_t *copps_list;
464	int ret, i, copp_idx;
465	/* Assumes port_ids have already been validated during adm_open */
466	struct q6copp *copp;
467	int pkt_size = (APR_HDR_SIZE + sizeof(*route) +  sizeof(*node) +
468		    (sizeof(uint32_t) * payload_map.num_copps));
469	void *matrix_map __free(kfree) = kzalloc(pkt_size, GFP_KERNEL);
470	if (!matrix_map)
471		return -ENOMEM;
472
473	pkt = matrix_map;
474	route = matrix_map + APR_HDR_SIZE;
475	node = matrix_map + APR_HDR_SIZE + sizeof(*route);
476	copps_list = matrix_map + APR_HDR_SIZE + sizeof(*route) + sizeof(*node);
477
478	pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD,
479					   APR_HDR_LEN(APR_HDR_SIZE),
480					   APR_PKT_VER);
481	pkt->hdr.pkt_size = pkt_size;
482	pkt->hdr.token = 0;
483	pkt->hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS_V5;
484	route->num_sessions = 1;
485
486	switch (path) {
487	case ADM_PATH_PLAYBACK:
488		route->matrix_id = ADM_MATRIX_ID_AUDIO_RX;
489		break;
490	case ADM_PATH_LIVE_REC:
491		route->matrix_id = ADM_MATRIX_ID_AUDIO_TX;
492		break;
493	default:
494		dev_err(dev, "Wrong path set[%d]\n", path);
495		break;
496	}
497
498	node->session_id = payload_map.session_id;
499	node->num_copps = payload_map.num_copps;
500
501	for (i = 0; i < payload_map.num_copps; i++) {
502		int port_idx = payload_map.port_id[i];
503
504		if (port_idx < 0) {
505			dev_err(dev, "Invalid port_id %d\n",
506				payload_map.port_id[i]);
507			return -EINVAL;
508		}
509		copp_idx = payload_map.copp_idx[i];
510
511		copp = q6adm_find_copp(adm, port_idx, copp_idx);
512		if (!copp)
513			return -EINVAL;
514
515		copps_list[i] = copp->id;
516		kref_put(&copp->refcount, q6adm_free_copp);
517	}
518
519	mutex_lock(&adm->lock);
520	adm->result.status = 0;
521	adm->result.opcode = 0;
522
523	ret = apr_send_pkt(adm->apr, pkt);
524	if (ret < 0) {
525		dev_err(dev, "routing for stream %d failed ret %d\n",
526		       payload_map.session_id, ret);
527		goto fail_cmd;
528	}
529	ret = wait_event_timeout(adm->matrix_map_wait,
530				 adm->result.opcode == pkt->hdr.opcode,
531				 msecs_to_jiffies(TIMEOUT_MS));
532	if (!ret) {
533		dev_err(dev, "routing for stream %d failed\n",
534		       payload_map.session_id);
535		ret = -ETIMEDOUT;
536		goto fail_cmd;
537	} else if (adm->result.status > 0) {
538		dev_err(dev, "DSP returned error[%d]\n",
539			adm->result.status);
540		ret = -EINVAL;
541		goto fail_cmd;
542	}
543
544fail_cmd:
545	mutex_unlock(&adm->lock);
546	return ret;
547}
548EXPORT_SYMBOL_GPL(q6adm_matrix_map);
549
550/**
551 * q6adm_close() - Close adm copp
552 *
553 * @dev: Pointer to adm child device.
554 * @copp: pointer to previously opened copp
555 *
556 * Return: Will be an negative on error or a zero on success.
557 */
558int q6adm_close(struct device *dev, struct q6copp *copp)
559{
560	kref_put(&copp->refcount, q6adm_free_copp);
561
562	return 0;
563}
564EXPORT_SYMBOL_GPL(q6adm_close);
565
566static int q6adm_probe(struct apr_device *adev)
567{
568	struct device *dev = &adev->dev;
569	struct q6adm *adm;
570
571	adm = devm_kzalloc(dev, sizeof(*adm), GFP_KERNEL);
572	if (!adm)
573		return -ENOMEM;
574
575	adm->apr = adev;
576	dev_set_drvdata(dev, adm);
577	adm->dev = dev;
578	q6core_get_svc_api_info(adev->svc_id, &adm->ainfo);
579	mutex_init(&adm->lock);
580	init_waitqueue_head(&adm->matrix_map_wait);
581
582	INIT_LIST_HEAD(&adm->copps_list);
583	spin_lock_init(&adm->copps_list_lock);
584
585	return devm_of_platform_populate(dev);
586}
587
588#ifdef CONFIG_OF
589static const struct of_device_id q6adm_device_id[]  = {
590	{ .compatible = "qcom,q6adm" },
591	{},
592};
593MODULE_DEVICE_TABLE(of, q6adm_device_id);
594#endif
595
596static struct apr_driver qcom_q6adm_driver = {
597	.probe = q6adm_probe,
598	.callback = q6adm_callback,
599	.driver = {
600		.name = "qcom-q6adm",
601		.of_match_table = of_match_ptr(q6adm_device_id),
602	},
603};
604
605module_apr_driver(qcom_q6adm_driver);
606MODULE_DESCRIPTION("Q6 Audio Device Manager");
607MODULE_LICENSE("GPL v2");
~/ Read-only file preview.