xref: /kernel/linux/linux-6.6/drivers/soc/qcom/apr.c (revision 62306a36)
162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
362306a36Sopenharmony_ci// Copyright (c) 2018, Linaro Limited
462306a36Sopenharmony_ci
562306a36Sopenharmony_ci#include <linux/kernel.h>
662306a36Sopenharmony_ci#include <linux/module.h>
762306a36Sopenharmony_ci#include <linux/device.h>
862306a36Sopenharmony_ci#include <linux/spinlock.h>
962306a36Sopenharmony_ci#include <linux/idr.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/workqueue.h>
1262306a36Sopenharmony_ci#include <linux/of_device.h>
1362306a36Sopenharmony_ci#include <linux/soc/qcom/apr.h>
1462306a36Sopenharmony_ci#include <linux/soc/qcom/pdr.h>
1562306a36Sopenharmony_ci#include <linux/rpmsg.h>
1662306a36Sopenharmony_ci#include <linux/of.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cienum {
1962306a36Sopenharmony_ci	PR_TYPE_APR = 0,
2062306a36Sopenharmony_ci	PR_TYPE_GPR,
2162306a36Sopenharmony_ci};
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/* Some random values tbh which does not collide with static modules */
2462306a36Sopenharmony_ci#define GPR_DYNAMIC_PORT_START	0x10000000
2562306a36Sopenharmony_ci#define GPR_DYNAMIC_PORT_END	0x20000000
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistruct packet_router {
2862306a36Sopenharmony_ci	struct rpmsg_endpoint *ch;
2962306a36Sopenharmony_ci	struct device *dev;
3062306a36Sopenharmony_ci	spinlock_t svcs_lock;
3162306a36Sopenharmony_ci	spinlock_t rx_lock;
3262306a36Sopenharmony_ci	struct idr svcs_idr;
3362306a36Sopenharmony_ci	int dest_domain_id;
3462306a36Sopenharmony_ci	int type;
3562306a36Sopenharmony_ci	struct pdr_handle *pdr;
3662306a36Sopenharmony_ci	struct workqueue_struct *rxwq;
3762306a36Sopenharmony_ci	struct work_struct rx_work;
3862306a36Sopenharmony_ci	struct list_head rx_list;
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistruct apr_rx_buf {
4262306a36Sopenharmony_ci	struct list_head node;
4362306a36Sopenharmony_ci	int len;
4462306a36Sopenharmony_ci	uint8_t buf[];
4562306a36Sopenharmony_ci};
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci/**
4862306a36Sopenharmony_ci * apr_send_pkt() - Send a apr message from apr device
4962306a36Sopenharmony_ci *
5062306a36Sopenharmony_ci * @adev: Pointer to previously registered apr device.
5162306a36Sopenharmony_ci * @pkt: Pointer to apr packet to send
5262306a36Sopenharmony_ci *
5362306a36Sopenharmony_ci * Return: Will be an negative on packet size on success.
5462306a36Sopenharmony_ci */
5562306a36Sopenharmony_ciint apr_send_pkt(struct apr_device *adev, struct apr_pkt *pkt)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	struct packet_router *apr = dev_get_drvdata(adev->dev.parent);
5862306a36Sopenharmony_ci	struct apr_hdr *hdr;
5962306a36Sopenharmony_ci	unsigned long flags;
6062306a36Sopenharmony_ci	int ret;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	spin_lock_irqsave(&adev->svc.lock, flags);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	hdr = &pkt->hdr;
6562306a36Sopenharmony_ci	hdr->src_domain = APR_DOMAIN_APPS;
6662306a36Sopenharmony_ci	hdr->src_svc = adev->svc.id;
6762306a36Sopenharmony_ci	hdr->dest_domain = adev->domain_id;
6862306a36Sopenharmony_ci	hdr->dest_svc = adev->svc.id;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	ret = rpmsg_trysend(apr->ch, pkt, hdr->pkt_size);
7162306a36Sopenharmony_ci	spin_unlock_irqrestore(&adev->svc.lock, flags);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return ret ? ret : hdr->pkt_size;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(apr_send_pkt);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_civoid gpr_free_port(gpr_port_t *port)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	struct packet_router *gpr = port->pr;
8062306a36Sopenharmony_ci	unsigned long flags;
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	spin_lock_irqsave(&gpr->svcs_lock, flags);
8362306a36Sopenharmony_ci	idr_remove(&gpr->svcs_idr, port->id);
8462306a36Sopenharmony_ci	spin_unlock_irqrestore(&gpr->svcs_lock, flags);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	kfree(port);
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gpr_free_port);
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cigpr_port_t *gpr_alloc_port(struct apr_device *gdev, struct device *dev,
9162306a36Sopenharmony_ci				gpr_port_cb cb,	void *priv)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	struct packet_router *pr = dev_get_drvdata(gdev->dev.parent);
9462306a36Sopenharmony_ci	gpr_port_t *port;
9562306a36Sopenharmony_ci	struct pkt_router_svc *svc;
9662306a36Sopenharmony_ci	int id;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	port = kzalloc(sizeof(*port), GFP_KERNEL);
9962306a36Sopenharmony_ci	if (!port)
10062306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci	svc = port;
10362306a36Sopenharmony_ci	svc->callback = cb;
10462306a36Sopenharmony_ci	svc->pr = pr;
10562306a36Sopenharmony_ci	svc->priv = priv;
10662306a36Sopenharmony_ci	svc->dev = dev;
10762306a36Sopenharmony_ci	spin_lock_init(&svc->lock);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	spin_lock(&pr->svcs_lock);
11062306a36Sopenharmony_ci	id = idr_alloc_cyclic(&pr->svcs_idr, svc, GPR_DYNAMIC_PORT_START,
11162306a36Sopenharmony_ci			      GPR_DYNAMIC_PORT_END, GFP_ATOMIC);
11262306a36Sopenharmony_ci	if (id < 0) {
11362306a36Sopenharmony_ci		dev_err(dev, "Unable to allocate dynamic GPR src port\n");
11462306a36Sopenharmony_ci		kfree(port);
11562306a36Sopenharmony_ci		spin_unlock(&pr->svcs_lock);
11662306a36Sopenharmony_ci		return ERR_PTR(id);
11762306a36Sopenharmony_ci	}
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	svc->id = id;
12062306a36Sopenharmony_ci	spin_unlock(&pr->svcs_lock);
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	return port;
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gpr_alloc_port);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_cistatic int pkt_router_send_svc_pkt(struct pkt_router_svc *svc, struct gpr_pkt *pkt)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	struct packet_router *pr = svc->pr;
12962306a36Sopenharmony_ci	struct gpr_hdr *hdr;
13062306a36Sopenharmony_ci	unsigned long flags;
13162306a36Sopenharmony_ci	int ret;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	hdr = &pkt->hdr;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	spin_lock_irqsave(&svc->lock, flags);
13662306a36Sopenharmony_ci	ret = rpmsg_trysend(pr->ch, pkt, hdr->pkt_size);
13762306a36Sopenharmony_ci	spin_unlock_irqrestore(&svc->lock, flags);
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	return ret ? ret : hdr->pkt_size;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ciint gpr_send_pkt(struct apr_device *gdev, struct gpr_pkt *pkt)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	return pkt_router_send_svc_pkt(&gdev->svc, pkt);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gpr_send_pkt);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ciint gpr_send_port_pkt(gpr_port_t *port, struct gpr_pkt *pkt)
14962306a36Sopenharmony_ci{
15062306a36Sopenharmony_ci	return pkt_router_send_svc_pkt(port, pkt);
15162306a36Sopenharmony_ci}
15262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(gpr_send_port_pkt);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic void apr_dev_release(struct device *dev)
15562306a36Sopenharmony_ci{
15662306a36Sopenharmony_ci	struct apr_device *adev = to_apr_device(dev);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	kfree(adev);
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic int apr_callback(struct rpmsg_device *rpdev, void *buf,
16262306a36Sopenharmony_ci				  int len, void *priv, u32 addr)
16362306a36Sopenharmony_ci{
16462306a36Sopenharmony_ci	struct packet_router *apr = dev_get_drvdata(&rpdev->dev);
16562306a36Sopenharmony_ci	struct apr_rx_buf *abuf;
16662306a36Sopenharmony_ci	unsigned long flags;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	if (len <= APR_HDR_SIZE) {
16962306a36Sopenharmony_ci		dev_err(apr->dev, "APR: Improper apr pkt received:%p %d\n",
17062306a36Sopenharmony_ci			buf, len);
17162306a36Sopenharmony_ci		return -EINVAL;
17262306a36Sopenharmony_ci	}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	abuf = kzalloc(sizeof(*abuf) + len, GFP_ATOMIC);
17562306a36Sopenharmony_ci	if (!abuf)
17662306a36Sopenharmony_ci		return -ENOMEM;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	abuf->len = len;
17962306a36Sopenharmony_ci	memcpy(abuf->buf, buf, len);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	spin_lock_irqsave(&apr->rx_lock, flags);
18262306a36Sopenharmony_ci	list_add_tail(&abuf->node, &apr->rx_list);
18362306a36Sopenharmony_ci	spin_unlock_irqrestore(&apr->rx_lock, flags);
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci	queue_work(apr->rxwq, &apr->rx_work);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	return 0;
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic int apr_do_rx_callback(struct packet_router *apr, struct apr_rx_buf *abuf)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	uint16_t hdr_size, msg_type, ver, svc_id;
19362306a36Sopenharmony_ci	struct pkt_router_svc *svc;
19462306a36Sopenharmony_ci	struct apr_device *adev;
19562306a36Sopenharmony_ci	struct apr_driver *adrv = NULL;
19662306a36Sopenharmony_ci	struct apr_resp_pkt resp;
19762306a36Sopenharmony_ci	struct apr_hdr *hdr;
19862306a36Sopenharmony_ci	unsigned long flags;
19962306a36Sopenharmony_ci	void *buf = abuf->buf;
20062306a36Sopenharmony_ci	int len = abuf->len;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	hdr = buf;
20362306a36Sopenharmony_ci	ver = APR_HDR_FIELD_VER(hdr->hdr_field);
20462306a36Sopenharmony_ci	if (ver > APR_PKT_VER + 1)
20562306a36Sopenharmony_ci		return -EINVAL;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	hdr_size = APR_HDR_FIELD_SIZE_BYTES(hdr->hdr_field);
20862306a36Sopenharmony_ci	if (hdr_size < APR_HDR_SIZE) {
20962306a36Sopenharmony_ci		dev_err(apr->dev, "APR: Wrong hdr size:%d\n", hdr_size);
21062306a36Sopenharmony_ci		return -EINVAL;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	if (hdr->pkt_size < APR_HDR_SIZE || hdr->pkt_size != len) {
21462306a36Sopenharmony_ci		dev_err(apr->dev, "APR: Wrong packet size\n");
21562306a36Sopenharmony_ci		return -EINVAL;
21662306a36Sopenharmony_ci	}
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	msg_type = APR_HDR_FIELD_MT(hdr->hdr_field);
21962306a36Sopenharmony_ci	if (msg_type >= APR_MSG_TYPE_MAX) {
22062306a36Sopenharmony_ci		dev_err(apr->dev, "APR: Wrong message type: %d\n", msg_type);
22162306a36Sopenharmony_ci		return -EINVAL;
22262306a36Sopenharmony_ci	}
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	if (hdr->src_domain >= APR_DOMAIN_MAX ||
22562306a36Sopenharmony_ci			hdr->dest_domain >= APR_DOMAIN_MAX ||
22662306a36Sopenharmony_ci			hdr->src_svc >= APR_SVC_MAX ||
22762306a36Sopenharmony_ci			hdr->dest_svc >= APR_SVC_MAX) {
22862306a36Sopenharmony_ci		dev_err(apr->dev, "APR: Wrong APR header\n");
22962306a36Sopenharmony_ci		return -EINVAL;
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	svc_id = hdr->dest_svc;
23362306a36Sopenharmony_ci	spin_lock_irqsave(&apr->svcs_lock, flags);
23462306a36Sopenharmony_ci	svc = idr_find(&apr->svcs_idr, svc_id);
23562306a36Sopenharmony_ci	if (svc && svc->dev->driver) {
23662306a36Sopenharmony_ci		adev = svc_to_apr_device(svc);
23762306a36Sopenharmony_ci		adrv = to_apr_driver(adev->dev.driver);
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci	spin_unlock_irqrestore(&apr->svcs_lock, flags);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	if (!adrv || !adev) {
24262306a36Sopenharmony_ci		dev_err(apr->dev, "APR: service is not registered (%d)\n",
24362306a36Sopenharmony_ci			svc_id);
24462306a36Sopenharmony_ci		return -EINVAL;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	resp.hdr = *hdr;
24862306a36Sopenharmony_ci	resp.payload_size = hdr->pkt_size - hdr_size;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	/*
25162306a36Sopenharmony_ci	 * NOTE: hdr_size is not same as APR_HDR_SIZE as remote can include
25262306a36Sopenharmony_ci	 * optional headers in to apr_hdr which should be ignored
25362306a36Sopenharmony_ci	 */
25462306a36Sopenharmony_ci	if (resp.payload_size > 0)
25562306a36Sopenharmony_ci		resp.payload = buf + hdr_size;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	adrv->callback(adev, &resp);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	return 0;
26062306a36Sopenharmony_ci}
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_cistatic int gpr_do_rx_callback(struct packet_router *gpr, struct apr_rx_buf *abuf)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	uint16_t hdr_size, ver;
26562306a36Sopenharmony_ci	struct pkt_router_svc *svc = NULL;
26662306a36Sopenharmony_ci	struct gpr_resp_pkt resp;
26762306a36Sopenharmony_ci	struct gpr_hdr *hdr;
26862306a36Sopenharmony_ci	unsigned long flags;
26962306a36Sopenharmony_ci	void *buf = abuf->buf;
27062306a36Sopenharmony_ci	int len = abuf->len;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	hdr = buf;
27362306a36Sopenharmony_ci	ver = hdr->version;
27462306a36Sopenharmony_ci	if (ver > GPR_PKT_VER + 1)
27562306a36Sopenharmony_ci		return -EINVAL;
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	hdr_size = hdr->hdr_size;
27862306a36Sopenharmony_ci	if (hdr_size < GPR_PKT_HEADER_WORD_SIZE) {
27962306a36Sopenharmony_ci		dev_err(gpr->dev, "GPR: Wrong hdr size:%d\n", hdr_size);
28062306a36Sopenharmony_ci		return -EINVAL;
28162306a36Sopenharmony_ci	}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci	if (hdr->pkt_size < GPR_PKT_HEADER_BYTE_SIZE || hdr->pkt_size != len) {
28462306a36Sopenharmony_ci		dev_err(gpr->dev, "GPR: Wrong packet size\n");
28562306a36Sopenharmony_ci		return -EINVAL;
28662306a36Sopenharmony_ci	}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	resp.hdr = *hdr;
28962306a36Sopenharmony_ci	resp.payload_size = hdr->pkt_size - (hdr_size * 4);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	/*
29262306a36Sopenharmony_ci	 * NOTE: hdr_size is not same as GPR_HDR_SIZE as remote can include
29362306a36Sopenharmony_ci	 * optional headers in to gpr_hdr which should be ignored
29462306a36Sopenharmony_ci	 */
29562306a36Sopenharmony_ci	if (resp.payload_size > 0)
29662306a36Sopenharmony_ci		resp.payload = buf + (hdr_size *  4);
29762306a36Sopenharmony_ci
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	spin_lock_irqsave(&gpr->svcs_lock, flags);
30062306a36Sopenharmony_ci	svc = idr_find(&gpr->svcs_idr, hdr->dest_port);
30162306a36Sopenharmony_ci	spin_unlock_irqrestore(&gpr->svcs_lock, flags);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	if (!svc) {
30462306a36Sopenharmony_ci		dev_err(gpr->dev, "GPR: Port(%x) is not registered\n",
30562306a36Sopenharmony_ci			hdr->dest_port);
30662306a36Sopenharmony_ci		return -EINVAL;
30762306a36Sopenharmony_ci	}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	if (svc->callback)
31062306a36Sopenharmony_ci		svc->callback(&resp, svc->priv, 0);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	return 0;
31362306a36Sopenharmony_ci}
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_cistatic void apr_rxwq(struct work_struct *work)
31662306a36Sopenharmony_ci{
31762306a36Sopenharmony_ci	struct packet_router *apr = container_of(work, struct packet_router, rx_work);
31862306a36Sopenharmony_ci	struct apr_rx_buf *abuf, *b;
31962306a36Sopenharmony_ci	unsigned long flags;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	if (!list_empty(&apr->rx_list)) {
32262306a36Sopenharmony_ci		list_for_each_entry_safe(abuf, b, &apr->rx_list, node) {
32362306a36Sopenharmony_ci			switch (apr->type) {
32462306a36Sopenharmony_ci			case PR_TYPE_APR:
32562306a36Sopenharmony_ci				apr_do_rx_callback(apr, abuf);
32662306a36Sopenharmony_ci				break;
32762306a36Sopenharmony_ci			case PR_TYPE_GPR:
32862306a36Sopenharmony_ci				gpr_do_rx_callback(apr, abuf);
32962306a36Sopenharmony_ci				break;
33062306a36Sopenharmony_ci			default:
33162306a36Sopenharmony_ci				break;
33262306a36Sopenharmony_ci			}
33362306a36Sopenharmony_ci			spin_lock_irqsave(&apr->rx_lock, flags);
33462306a36Sopenharmony_ci			list_del(&abuf->node);
33562306a36Sopenharmony_ci			spin_unlock_irqrestore(&apr->rx_lock, flags);
33662306a36Sopenharmony_ci			kfree(abuf);
33762306a36Sopenharmony_ci		}
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic int apr_device_match(struct device *dev, struct device_driver *drv)
34262306a36Sopenharmony_ci{
34362306a36Sopenharmony_ci	struct apr_device *adev = to_apr_device(dev);
34462306a36Sopenharmony_ci	struct apr_driver *adrv = to_apr_driver(drv);
34562306a36Sopenharmony_ci	const struct apr_device_id *id = adrv->id_table;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	/* Attempt an OF style match first */
34862306a36Sopenharmony_ci	if (of_driver_match_device(dev, drv))
34962306a36Sopenharmony_ci		return 1;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	if (!id)
35262306a36Sopenharmony_ci		return 0;
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	while (id->domain_id != 0 || id->svc_id != 0) {
35562306a36Sopenharmony_ci		if (id->domain_id == adev->domain_id &&
35662306a36Sopenharmony_ci		    id->svc_id == adev->svc.id)
35762306a36Sopenharmony_ci			return 1;
35862306a36Sopenharmony_ci		id++;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	return 0;
36262306a36Sopenharmony_ci}
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_cistatic int apr_device_probe(struct device *dev)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	struct apr_device *adev = to_apr_device(dev);
36762306a36Sopenharmony_ci	struct apr_driver *adrv = to_apr_driver(dev->driver);
36862306a36Sopenharmony_ci	int ret;
36962306a36Sopenharmony_ci
37062306a36Sopenharmony_ci	ret = adrv->probe(adev);
37162306a36Sopenharmony_ci	if (!ret)
37262306a36Sopenharmony_ci		adev->svc.callback = adrv->gpr_callback;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	return ret;
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_cistatic void apr_device_remove(struct device *dev)
37862306a36Sopenharmony_ci{
37962306a36Sopenharmony_ci	struct apr_device *adev = to_apr_device(dev);
38062306a36Sopenharmony_ci	struct apr_driver *adrv = to_apr_driver(dev->driver);
38162306a36Sopenharmony_ci	struct packet_router *apr = dev_get_drvdata(adev->dev.parent);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	if (adrv->remove)
38462306a36Sopenharmony_ci		adrv->remove(adev);
38562306a36Sopenharmony_ci	spin_lock(&apr->svcs_lock);
38662306a36Sopenharmony_ci	idr_remove(&apr->svcs_idr, adev->svc.id);
38762306a36Sopenharmony_ci	spin_unlock(&apr->svcs_lock);
38862306a36Sopenharmony_ci}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_cistatic int apr_uevent(const struct device *dev, struct kobj_uevent_env *env)
39162306a36Sopenharmony_ci{
39262306a36Sopenharmony_ci	const struct apr_device *adev = to_apr_device(dev);
39362306a36Sopenharmony_ci	int ret;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	ret = of_device_uevent_modalias(dev, env);
39662306a36Sopenharmony_ci	if (ret != -ENODEV)
39762306a36Sopenharmony_ci		return ret;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	return add_uevent_var(env, "MODALIAS=apr:%s", adev->name);
40062306a36Sopenharmony_ci}
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_cistruct bus_type aprbus = {
40362306a36Sopenharmony_ci	.name		= "aprbus",
40462306a36Sopenharmony_ci	.match		= apr_device_match,
40562306a36Sopenharmony_ci	.probe		= apr_device_probe,
40662306a36Sopenharmony_ci	.uevent		= apr_uevent,
40762306a36Sopenharmony_ci	.remove		= apr_device_remove,
40862306a36Sopenharmony_ci};
40962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(aprbus);
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cistatic int apr_add_device(struct device *dev, struct device_node *np,
41262306a36Sopenharmony_ci			  u32 svc_id, u32 domain_id)
41362306a36Sopenharmony_ci{
41462306a36Sopenharmony_ci	struct packet_router *apr = dev_get_drvdata(dev);
41562306a36Sopenharmony_ci	struct apr_device *adev = NULL;
41662306a36Sopenharmony_ci	struct pkt_router_svc *svc;
41762306a36Sopenharmony_ci	int ret;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	adev = kzalloc(sizeof(*adev), GFP_KERNEL);
42062306a36Sopenharmony_ci	if (!adev)
42162306a36Sopenharmony_ci		return -ENOMEM;
42262306a36Sopenharmony_ci
42362306a36Sopenharmony_ci	adev->svc_id = svc_id;
42462306a36Sopenharmony_ci	svc = &adev->svc;
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	svc->id = svc_id;
42762306a36Sopenharmony_ci	svc->pr = apr;
42862306a36Sopenharmony_ci	svc->priv = adev;
42962306a36Sopenharmony_ci	svc->dev = dev;
43062306a36Sopenharmony_ci	spin_lock_init(&svc->lock);
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	adev->domain_id = domain_id;
43362306a36Sopenharmony_ci
43462306a36Sopenharmony_ci	if (np)
43562306a36Sopenharmony_ci		snprintf(adev->name, APR_NAME_SIZE, "%pOFn", np);
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	switch (apr->type) {
43862306a36Sopenharmony_ci	case PR_TYPE_APR:
43962306a36Sopenharmony_ci		dev_set_name(&adev->dev, "aprsvc:%s:%x:%x", adev->name,
44062306a36Sopenharmony_ci			     domain_id, svc_id);
44162306a36Sopenharmony_ci		break;
44262306a36Sopenharmony_ci	case PR_TYPE_GPR:
44362306a36Sopenharmony_ci		dev_set_name(&adev->dev, "gprsvc:%s:%x:%x", adev->name,
44462306a36Sopenharmony_ci			     domain_id, svc_id);
44562306a36Sopenharmony_ci		break;
44662306a36Sopenharmony_ci	default:
44762306a36Sopenharmony_ci		break;
44862306a36Sopenharmony_ci	}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	adev->dev.bus = &aprbus;
45162306a36Sopenharmony_ci	adev->dev.parent = dev;
45262306a36Sopenharmony_ci	adev->dev.of_node = np;
45362306a36Sopenharmony_ci	adev->dev.release = apr_dev_release;
45462306a36Sopenharmony_ci	adev->dev.driver = NULL;
45562306a36Sopenharmony_ci
45662306a36Sopenharmony_ci	spin_lock(&apr->svcs_lock);
45762306a36Sopenharmony_ci	ret = idr_alloc(&apr->svcs_idr, svc, svc_id, svc_id + 1, GFP_ATOMIC);
45862306a36Sopenharmony_ci	spin_unlock(&apr->svcs_lock);
45962306a36Sopenharmony_ci	if (ret < 0) {
46062306a36Sopenharmony_ci		dev_err(dev, "idr_alloc failed: %d\n", ret);
46162306a36Sopenharmony_ci		goto out;
46262306a36Sopenharmony_ci	}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	/* Protection domain is optional, it does not exist on older platforms */
46562306a36Sopenharmony_ci	ret = of_property_read_string_index(np, "qcom,protection-domain",
46662306a36Sopenharmony_ci					    1, &adev->service_path);
46762306a36Sopenharmony_ci	if (ret < 0 && ret != -EINVAL) {
46862306a36Sopenharmony_ci		dev_err(dev, "Failed to read second value of qcom,protection-domain\n");
46962306a36Sopenharmony_ci		goto out;
47062306a36Sopenharmony_ci	}
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci	dev_info(dev, "Adding APR/GPR dev: %s\n", dev_name(&adev->dev));
47362306a36Sopenharmony_ci
47462306a36Sopenharmony_ci	ret = device_register(&adev->dev);
47562306a36Sopenharmony_ci	if (ret) {
47662306a36Sopenharmony_ci		dev_err(dev, "device_register failed: %d\n", ret);
47762306a36Sopenharmony_ci		put_device(&adev->dev);
47862306a36Sopenharmony_ci	}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_ciout:
48162306a36Sopenharmony_ci	return ret;
48262306a36Sopenharmony_ci}
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_cistatic int of_apr_add_pd_lookups(struct device *dev)
48562306a36Sopenharmony_ci{
48662306a36Sopenharmony_ci	const char *service_name, *service_path;
48762306a36Sopenharmony_ci	struct packet_router *apr = dev_get_drvdata(dev);
48862306a36Sopenharmony_ci	struct device_node *node;
48962306a36Sopenharmony_ci	struct pdr_service *pds;
49062306a36Sopenharmony_ci	int ret;
49162306a36Sopenharmony_ci
49262306a36Sopenharmony_ci	for_each_child_of_node(dev->of_node, node) {
49362306a36Sopenharmony_ci		ret = of_property_read_string_index(node, "qcom,protection-domain",
49462306a36Sopenharmony_ci						    0, &service_name);
49562306a36Sopenharmony_ci		if (ret < 0)
49662306a36Sopenharmony_ci			continue;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci		ret = of_property_read_string_index(node, "qcom,protection-domain",
49962306a36Sopenharmony_ci						    1, &service_path);
50062306a36Sopenharmony_ci		if (ret < 0) {
50162306a36Sopenharmony_ci			dev_err(dev, "pdr service path missing: %d\n", ret);
50262306a36Sopenharmony_ci			of_node_put(node);
50362306a36Sopenharmony_ci			return ret;
50462306a36Sopenharmony_ci		}
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci		pds = pdr_add_lookup(apr->pdr, service_name, service_path);
50762306a36Sopenharmony_ci		if (IS_ERR(pds) && PTR_ERR(pds) != -EALREADY) {
50862306a36Sopenharmony_ci			dev_err(dev, "pdr add lookup failed: %ld\n", PTR_ERR(pds));
50962306a36Sopenharmony_ci			of_node_put(node);
51062306a36Sopenharmony_ci			return PTR_ERR(pds);
51162306a36Sopenharmony_ci		}
51262306a36Sopenharmony_ci	}
51362306a36Sopenharmony_ci
51462306a36Sopenharmony_ci	return 0;
51562306a36Sopenharmony_ci}
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_cistatic void of_register_apr_devices(struct device *dev, const char *svc_path)
51862306a36Sopenharmony_ci{
51962306a36Sopenharmony_ci	struct packet_router *apr = dev_get_drvdata(dev);
52062306a36Sopenharmony_ci	struct device_node *node;
52162306a36Sopenharmony_ci	const char *service_path;
52262306a36Sopenharmony_ci	int ret;
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_ci	for_each_child_of_node(dev->of_node, node) {
52562306a36Sopenharmony_ci		u32 svc_id;
52662306a36Sopenharmony_ci		u32 domain_id;
52762306a36Sopenharmony_ci
52862306a36Sopenharmony_ci		/*
52962306a36Sopenharmony_ci		 * This function is called with svc_path NULL during
53062306a36Sopenharmony_ci		 * apr_probe(), in which case we register any apr devices
53162306a36Sopenharmony_ci		 * without a qcom,protection-domain specified.
53262306a36Sopenharmony_ci		 *
53362306a36Sopenharmony_ci		 * Then as the protection domains becomes available
53462306a36Sopenharmony_ci		 * (if applicable) this function is again called, but with
53562306a36Sopenharmony_ci		 * svc_path representing the service becoming available. In
53662306a36Sopenharmony_ci		 * this case we register any apr devices with a matching
53762306a36Sopenharmony_ci		 * qcom,protection-domain.
53862306a36Sopenharmony_ci		 */
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci		ret = of_property_read_string_index(node, "qcom,protection-domain",
54162306a36Sopenharmony_ci						    1, &service_path);
54262306a36Sopenharmony_ci		if (svc_path) {
54362306a36Sopenharmony_ci			/* skip APR services that are PD independent */
54462306a36Sopenharmony_ci			if (ret)
54562306a36Sopenharmony_ci				continue;
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_ci			/* skip APR services whose PD paths don't match */
54862306a36Sopenharmony_ci			if (strcmp(service_path, svc_path))
54962306a36Sopenharmony_ci				continue;
55062306a36Sopenharmony_ci		} else {
55162306a36Sopenharmony_ci			/* skip APR services whose PD lookups are registered */
55262306a36Sopenharmony_ci			if (ret == 0)
55362306a36Sopenharmony_ci				continue;
55462306a36Sopenharmony_ci		}
55562306a36Sopenharmony_ci
55662306a36Sopenharmony_ci		if (of_property_read_u32(node, "reg", &svc_id))
55762306a36Sopenharmony_ci			continue;
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_ci		domain_id = apr->dest_domain_id;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci		if (apr_add_device(dev, node, svc_id, domain_id))
56262306a36Sopenharmony_ci			dev_err(dev, "Failed to add apr %d svc\n", svc_id);
56362306a36Sopenharmony_ci	}
56462306a36Sopenharmony_ci}
56562306a36Sopenharmony_ci
56662306a36Sopenharmony_cistatic int apr_remove_device(struct device *dev, void *svc_path)
56762306a36Sopenharmony_ci{
56862306a36Sopenharmony_ci	struct apr_device *adev = to_apr_device(dev);
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	if (svc_path && adev->service_path) {
57162306a36Sopenharmony_ci		if (!strcmp(adev->service_path, (char *)svc_path))
57262306a36Sopenharmony_ci			device_unregister(&adev->dev);
57362306a36Sopenharmony_ci	} else {
57462306a36Sopenharmony_ci		device_unregister(&adev->dev);
57562306a36Sopenharmony_ci	}
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	return 0;
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cistatic void apr_pd_status(int state, char *svc_path, void *priv)
58162306a36Sopenharmony_ci{
58262306a36Sopenharmony_ci	struct packet_router *apr = (struct packet_router *)priv;
58362306a36Sopenharmony_ci
58462306a36Sopenharmony_ci	switch (state) {
58562306a36Sopenharmony_ci	case SERVREG_SERVICE_STATE_UP:
58662306a36Sopenharmony_ci		of_register_apr_devices(apr->dev, svc_path);
58762306a36Sopenharmony_ci		break;
58862306a36Sopenharmony_ci	case SERVREG_SERVICE_STATE_DOWN:
58962306a36Sopenharmony_ci		device_for_each_child(apr->dev, svc_path, apr_remove_device);
59062306a36Sopenharmony_ci		break;
59162306a36Sopenharmony_ci	}
59262306a36Sopenharmony_ci}
59362306a36Sopenharmony_ci
59462306a36Sopenharmony_cistatic int apr_probe(struct rpmsg_device *rpdev)
59562306a36Sopenharmony_ci{
59662306a36Sopenharmony_ci	struct device *dev = &rpdev->dev;
59762306a36Sopenharmony_ci	struct packet_router *apr;
59862306a36Sopenharmony_ci	int ret;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	apr = devm_kzalloc(dev, sizeof(*apr), GFP_KERNEL);
60162306a36Sopenharmony_ci	if (!apr)
60262306a36Sopenharmony_ci		return -ENOMEM;
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	ret = of_property_read_u32(dev->of_node, "qcom,domain", &apr->dest_domain_id);
60562306a36Sopenharmony_ci
60662306a36Sopenharmony_ci	if (of_device_is_compatible(dev->of_node, "qcom,gpr")) {
60762306a36Sopenharmony_ci		apr->type = PR_TYPE_GPR;
60862306a36Sopenharmony_ci	} else {
60962306a36Sopenharmony_ci		if (ret) /* try deprecated apr-domain property */
61062306a36Sopenharmony_ci			ret = of_property_read_u32(dev->of_node, "qcom,apr-domain",
61162306a36Sopenharmony_ci						   &apr->dest_domain_id);
61262306a36Sopenharmony_ci		apr->type = PR_TYPE_APR;
61362306a36Sopenharmony_ci	}
61462306a36Sopenharmony_ci
61562306a36Sopenharmony_ci	if (ret) {
61662306a36Sopenharmony_ci		dev_err(dev, "Domain ID not specified in DT\n");
61762306a36Sopenharmony_ci		return ret;
61862306a36Sopenharmony_ci	}
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	dev_set_drvdata(dev, apr);
62162306a36Sopenharmony_ci	apr->ch = rpdev->ept;
62262306a36Sopenharmony_ci	apr->dev = dev;
62362306a36Sopenharmony_ci	apr->rxwq = create_singlethread_workqueue("qcom_apr_rx");
62462306a36Sopenharmony_ci	if (!apr->rxwq) {
62562306a36Sopenharmony_ci		dev_err(apr->dev, "Failed to start Rx WQ\n");
62662306a36Sopenharmony_ci		return -ENOMEM;
62762306a36Sopenharmony_ci	}
62862306a36Sopenharmony_ci	INIT_WORK(&apr->rx_work, apr_rxwq);
62962306a36Sopenharmony_ci
63062306a36Sopenharmony_ci	apr->pdr = pdr_handle_alloc(apr_pd_status, apr);
63162306a36Sopenharmony_ci	if (IS_ERR(apr->pdr)) {
63262306a36Sopenharmony_ci		dev_err(dev, "Failed to init PDR handle\n");
63362306a36Sopenharmony_ci		ret = PTR_ERR(apr->pdr);
63462306a36Sopenharmony_ci		goto destroy_wq;
63562306a36Sopenharmony_ci	}
63662306a36Sopenharmony_ci
63762306a36Sopenharmony_ci	INIT_LIST_HEAD(&apr->rx_list);
63862306a36Sopenharmony_ci	spin_lock_init(&apr->rx_lock);
63962306a36Sopenharmony_ci	spin_lock_init(&apr->svcs_lock);
64062306a36Sopenharmony_ci	idr_init(&apr->svcs_idr);
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_ci	ret = of_apr_add_pd_lookups(dev);
64362306a36Sopenharmony_ci	if (ret)
64462306a36Sopenharmony_ci		goto handle_release;
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	of_register_apr_devices(dev, NULL);
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	return 0;
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_cihandle_release:
65162306a36Sopenharmony_ci	pdr_handle_release(apr->pdr);
65262306a36Sopenharmony_cidestroy_wq:
65362306a36Sopenharmony_ci	destroy_workqueue(apr->rxwq);
65462306a36Sopenharmony_ci	return ret;
65562306a36Sopenharmony_ci}
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_cistatic void apr_remove(struct rpmsg_device *rpdev)
65862306a36Sopenharmony_ci{
65962306a36Sopenharmony_ci	struct packet_router *apr = dev_get_drvdata(&rpdev->dev);
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	pdr_handle_release(apr->pdr);
66262306a36Sopenharmony_ci	device_for_each_child(&rpdev->dev, NULL, apr_remove_device);
66362306a36Sopenharmony_ci	destroy_workqueue(apr->rxwq);
66462306a36Sopenharmony_ci}
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci/*
66762306a36Sopenharmony_ci * __apr_driver_register() - Client driver registration with aprbus
66862306a36Sopenharmony_ci *
66962306a36Sopenharmony_ci * @drv:Client driver to be associated with client-device.
67062306a36Sopenharmony_ci * @owner: owning module/driver
67162306a36Sopenharmony_ci *
67262306a36Sopenharmony_ci * This API will register the client driver with the aprbus
67362306a36Sopenharmony_ci * It is called from the driver's module-init function.
67462306a36Sopenharmony_ci */
67562306a36Sopenharmony_ciint __apr_driver_register(struct apr_driver *drv, struct module *owner)
67662306a36Sopenharmony_ci{
67762306a36Sopenharmony_ci	drv->driver.bus = &aprbus;
67862306a36Sopenharmony_ci	drv->driver.owner = owner;
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci	return driver_register(&drv->driver);
68162306a36Sopenharmony_ci}
68262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__apr_driver_register);
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci/*
68562306a36Sopenharmony_ci * apr_driver_unregister() - Undo effect of apr_driver_register
68662306a36Sopenharmony_ci *
68762306a36Sopenharmony_ci * @drv: Client driver to be unregistered
68862306a36Sopenharmony_ci */
68962306a36Sopenharmony_civoid apr_driver_unregister(struct apr_driver *drv)
69062306a36Sopenharmony_ci{
69162306a36Sopenharmony_ci	driver_unregister(&drv->driver);
69262306a36Sopenharmony_ci}
69362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(apr_driver_unregister);
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_cistatic const struct of_device_id pkt_router_of_match[] = {
69662306a36Sopenharmony_ci	{ .compatible = "qcom,apr"},
69762306a36Sopenharmony_ci	{ .compatible = "qcom,apr-v2"},
69862306a36Sopenharmony_ci	{ .compatible = "qcom,gpr"},
69962306a36Sopenharmony_ci	{}
70062306a36Sopenharmony_ci};
70162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, pkt_router_of_match);
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_cistatic struct rpmsg_driver packet_router_driver = {
70462306a36Sopenharmony_ci	.probe = apr_probe,
70562306a36Sopenharmony_ci	.remove = apr_remove,
70662306a36Sopenharmony_ci	.callback = apr_callback,
70762306a36Sopenharmony_ci	.drv = {
70862306a36Sopenharmony_ci		.name = "qcom,apr",
70962306a36Sopenharmony_ci		.of_match_table = pkt_router_of_match,
71062306a36Sopenharmony_ci	},
71162306a36Sopenharmony_ci};
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_cistatic int __init apr_init(void)
71462306a36Sopenharmony_ci{
71562306a36Sopenharmony_ci	int ret;
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci	ret = bus_register(&aprbus);
71862306a36Sopenharmony_ci	if (!ret)
71962306a36Sopenharmony_ci		ret = register_rpmsg_driver(&packet_router_driver);
72062306a36Sopenharmony_ci	else
72162306a36Sopenharmony_ci		bus_unregister(&aprbus);
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci	return ret;
72462306a36Sopenharmony_ci}
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_cistatic void __exit apr_exit(void)
72762306a36Sopenharmony_ci{
72862306a36Sopenharmony_ci	bus_unregister(&aprbus);
72962306a36Sopenharmony_ci	unregister_rpmsg_driver(&packet_router_driver);
73062306a36Sopenharmony_ci}
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_cisubsys_initcall(apr_init);
73362306a36Sopenharmony_cimodule_exit(apr_exit);
73462306a36Sopenharmony_ci
73562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
73662306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm APR Bus");
737