| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/err.h> |
| #include <linux/export.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/printk.h> |
| #include <linux/mailbox_client.h> |
| #include <linux/amlogic/scpi_protocol.h> |
| #include <linux/slab.h> |
| #include "meson_mhu_dsp.h" |
| |
| #define CMD_TAG (2 << 25) |
| |
| struct scpi_data_buf { |
| int client_id; |
| struct mhu_data_buf *data; |
| struct completion complete; |
| }; |
| |
| enum scpi_error_codes { |
| SCPI_SUCCESS = 0, /* Success */ |
| SCPI_ERR_PARAM = 1, /* Invalid parameter(s) */ |
| SCPI_ERR_ALIGN = 2, /* Invalid alignment */ |
| SCPI_ERR_SIZE = 3, /* Invalid size */ |
| SCPI_ERR_HANDLER = 4, /* Invalid handler/callback */ |
| SCPI_ERR_ACCESS = 5, /* Invalid access/permission denied */ |
| SCPI_ERR_RANGE = 6, /* Value out of range */ |
| SCPI_ERR_TIMEOUT = 7, /* Timeout has occurred */ |
| SCPI_ERR_NOMEM = 8, /* Invalid memory area or pointer */ |
| SCPI_ERR_PWRSTATE = 9, /* Invalid power state */ |
| SCPI_ERR_SUPPORT = 10, /* Not supported or disabled */ |
| SCPI_ERR_DEVICE = 11, /* Device error */ |
| SCPI_ERR_MAX |
| }; |
| |
| static int scpi_linux_errmap[SCPI_ERR_MAX] = { |
| 0, -EINVAL, -ENOEXEC, -EMSGSIZE, |
| -EINVAL, -EACCES, -ERANGE, -ETIMEDOUT, |
| -ENOMEM, -EINVAL, -EOPNOTSUPP, -EIO, |
| }; |
| |
| static inline int scpi_to_linux_errno(int errno) |
| { |
| if (errno >= SCPI_SUCCESS && errno < SCPI_ERR_MAX) |
| return scpi_linux_errmap[errno]; |
| return -EIO; |
| } |
| |
| static void scpi_rx_callback(struct mbox_client *cl, void *msg) |
| { |
| struct mhu_data_buf *data = (struct mhu_data_buf *)msg; |
| struct scpi_data_buf *scpi_buf = data->cl_data; |
| |
| complete(&scpi_buf->complete); |
| } |
| |
| static int send_scpi_cmd(struct scpi_data_buf *scpi_buf, int client_id) |
| { |
| struct mbox_chan *chan; |
| struct mbox_client cl = {0}; |
| struct mhu_data_buf *data = scpi_buf->data; |
| u32 status; |
| |
| cl.dev = dsp_scpi_device; |
| cl.rx_callback = scpi_rx_callback; |
| pr_debug("dsp %p\n", dsp_scpi_device); |
| chan = mbox_request_channel(&cl, client_id); |
| if (IS_ERR(chan)) |
| return PTR_ERR(chan); |
| |
| init_completion(&scpi_buf->complete); |
| if (mbox_send_message(chan, (void *)data) < 0) { |
| status = SCPI_ERR_TIMEOUT; |
| goto free_channel; |
| } |
| |
| wait_for_completion(&scpi_buf->complete); |
| status = *(u32 *)(data->rx_buf); /* read first word */ |
| |
| free_channel: |
| mbox_free_channel(chan); |
| |
| return scpi_to_linux_errno(status); |
| } |
| |
| #define SCPI_SETUP_DBUF_SIZE(scpi_buf, mhu_buf, _client_id,\ |
| _cmd, _tx_buf, _tx_size, _rx_buf, _rx_size) \ |
| do { \ |
| struct mhu_data_buf *pdata = &(mhu_buf);\ |
| pdata->cmd = _cmd; \ |
| pdata->tx_buf = _tx_buf; \ |
| pdata->tx_size = _tx_size; \ |
| pdata->rx_buf = _rx_buf; \ |
| pdata->rx_size = _rx_size; \ |
| scpi_buf.client_id = _client_id; \ |
| scpi_buf.data = pdata; \ |
| } while (0) |
| |
| static int scpi_execute_cmd(struct scpi_data_buf *scpi_buf) |
| { |
| struct mhu_data_buf *data; |
| |
| if (!scpi_buf || !scpi_buf->data) |
| return -EINVAL; |
| data = scpi_buf->data; |
| data->cmd = (data->cmd & 0xffff) |
| | (data->tx_size & 0x1ff) << 16 |
| | CMD_TAG; //Sync Flag |
| data->cl_data = scpi_buf; |
| |
| return send_scpi_cmd(scpi_buf, scpi_buf->client_id); |
| } |
| |
| int scpi_send_dsp_data(void *data, int size, bool to_dspa) |
| { |
| struct scpi_data_buf sdata; |
| struct mhu_data_buf mdata; |
| int client_id = 0; |
| |
| struct __packed { |
| u32 status; |
| } buf; |
| |
| if (!to_dspa) |
| client_id = 3; |
| else |
| client_id = 1; |
| |
| SCPI_SETUP_DBUF_SIZE(sdata, mdata, client_id, |
| SCPI_CMD_SEND_DSP_DATA, data, |
| size, &buf, sizeof(buf)); |
| if (scpi_execute_cmd(&sdata)) |
| return -EPERM; |
| return buf.status; |
| } |
| EXPORT_SYMBOL_GPL(scpi_send_dsp_data); |
| |
| int scpi_send_dsp_cmd(void *data, int size, bool to_dspa, |
| int cmd, int taskid) |
| { |
| struct scpi_data_buf sdata; |
| struct mhu_data_buf mdata; |
| int client_id = 0; |
| |
| struct __packed { |
| u32 status; |
| } buf; |
| |
| struct txbuf { |
| u64 taskid; |
| char data[240]; |
| } txbuf; |
| |
| if (!to_dspa) |
| client_id = 3; |
| else |
| client_id = 1; |
| |
| txbuf.taskid = taskid; |
| memcpy(txbuf.data, data, size); |
| |
| SCPI_SETUP_DBUF_SIZE(sdata, mdata, client_id, |
| cmd, (void *)&txbuf, |
| sizeof(txbuf), &buf, sizeof(buf)); |
| if (scpi_execute_cmd(&sdata)) |
| return -EPERM; |
| return buf.status; |
| } |
| EXPORT_SYMBOL_GPL(scpi_send_dsp_cmd); |
| |
| int scpi_req_handle(void *p, u32 size, u32 cmd, int idx) |
| { |
| int ret = 0; |
| unsigned int dspid = 0; |
| struct hifi4syslog *phifilogbuf = NULL; |
| |
| if (BIT(idx) & 0xc) |
| dspid = 1; |
| else if (BIT(idx) & 0x3) |
| dspid = 0; |
| else |
| dspid = 0; |
| |
| switch (cmd) { |
| case SCPI_REQ_INVALID: |
| break; |
| case SCPI_CMD_HIFI4SYSTLOG: |
| phifilogbuf = (struct hifi4syslog *)p; |
| strcpy(hifi4logbuffer[dspid].syslogstate, |
| phifilogbuf->syslogstate); |
| hifi4logbuffer[dspid].logbaseaddr = phifilogbuf->logbaseaddr; |
| hifi4logbuffer[dspid].syslogsize = phifilogbuf->syslogsize; |
| hifi4logbuffer[dspid].loghead = phifilogbuf->loghead; |
| hifi4logbuffer[dspid].logtail = phifilogbuf->logtail; |
| ret = 1; |
| break; |
| case SCPI_CMD_HIFI4STOP: |
| ret = 1; |
| break; |
| default: |
| break; |
| } |
| return ret; |
| } |