slim_msm: Add QMI messaging support
QMI messaging is used to communicate with the LPASS SLIMbus driver to
handle master/slave configuration and voting for power control.
Change-Id: I5e6d8f1a405a99afe6e1d81495dd46d7ae983aa5
Signed-off-by: Kenneth Heitke <kheitke@codeaurora.org>
diff --git a/drivers/slimbus/slim-msm.c b/drivers/slimbus/slim-msm.c
index 7cd34d3..c62ac27 100644
--- a/drivers/slimbus/slim-msm.c
+++ b/drivers/slimbus/slim-msm.c
@@ -582,3 +582,351 @@
sps_deregister_bam_device(dev->bam.hdl);
}
}
+
+/* Slimbus QMI Messaging */
+#define SLIMBUS_QMI_SELECT_INSTANCE_REQ_V01 0x0020
+#define SLIMBUS_QMI_SELECT_INSTANCE_RESP_V01 0x0020
+#define SLIMBUS_QMI_POWER_REQ_V01 0x0021
+#define SLIMBUS_QMI_POWER_RESP_V01 0x0021
+
+enum slimbus_mode_enum_type_v01 {
+ /* To force a 32 bit signed enum. Do not change or use*/
+ SLIMBUS_MODE_ENUM_TYPE_MIN_ENUM_VAL_V01 = INT_MIN,
+ SLIMBUS_MODE_SATELLITE_V01 = 1,
+ SLIMBUS_MODE_MASTER_V01 = 2,
+ SLIMBUS_MODE_ENUM_TYPE_MAX_ENUM_VAL_V01 = INT_MAX,
+};
+
+enum slimbus_pm_enum_type_v01 {
+ /* To force a 32 bit signed enum. Do not change or use*/
+ SLIMBUS_PM_ENUM_TYPE_MIN_ENUM_VAL_V01 = INT_MIN,
+ SLIMBUS_PM_INACTIVE_V01 = 1,
+ SLIMBUS_PM_ACTIVE_V01 = 2,
+ SLIMBUS_PM_ENUM_TYPE_MAX_ENUM_VAL_V01 = INT_MAX,
+};
+
+struct slimbus_select_inst_req_msg_v01 {
+ /* Mandatory */
+ /* Hardware Instance Selection */
+ uint32_t instance;
+
+ /* Optional */
+ /* Optional Mode Request Operation */
+ /* Must be set to true if mode is being passed */
+ uint8_t mode_valid;
+ enum slimbus_mode_enum_type_v01 mode;
+};
+
+struct slimbus_select_inst_resp_msg_v01 {
+ /* Mandatory */
+ /* Result Code */
+ struct qmi_response_type_v01 resp;
+};
+
+struct slimbus_power_req_msg_v01 {
+ /* Mandatory */
+ /* Power Request Operation */
+ enum slimbus_pm_enum_type_v01 pm_req;
+};
+
+struct slimbus_power_resp_msg_v01 {
+ /* Mandatory */
+ /* Result Code */
+ struct qmi_response_type_v01 resp;
+};
+
+static struct elem_info slimbus_select_inst_req_msg_v01_ei[] = {
+ {
+ .data_type = QMI_UNSIGNED_4_BYTE,
+ .elem_len = 1,
+ .elem_size = sizeof(uint32_t),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x01,
+ .offset = offsetof(struct slimbus_select_inst_req_msg_v01,
+ instance),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_OPT_FLAG,
+ .elem_len = 1,
+ .elem_size = sizeof(uint8_t),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(struct slimbus_select_inst_req_msg_v01,
+ mode_valid),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_UNSIGNED_4_BYTE,
+ .elem_len = 1,
+ .elem_size = sizeof(enum slimbus_mode_enum_type_v01),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x10,
+ .offset = offsetof(struct slimbus_select_inst_req_msg_v01,
+ mode),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_EOTI,
+ .elem_len = 0,
+ .elem_size = 0,
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x00,
+ .offset = 0,
+ .ei_array = NULL,
+ },
+};
+
+static struct elem_info slimbus_select_inst_resp_msg_v01_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct qmi_response_type_v01),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(struct slimbus_select_inst_resp_msg_v01,
+ resp),
+ .ei_array = get_qmi_response_type_v01_ei(),
+ },
+ {
+ .data_type = QMI_EOTI,
+ .elem_len = 0,
+ .elem_size = 0,
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x00,
+ .offset = 0,
+ .ei_array = NULL,
+ },
+};
+
+static struct elem_info slimbus_power_req_msg_v01_ei[] = {
+ {
+ .data_type = QMI_UNSIGNED_4_BYTE,
+ .elem_len = 1,
+ .elem_size = sizeof(enum slimbus_pm_enum_type_v01),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x01,
+ .offset = offsetof(struct slimbus_power_req_msg_v01, pm_req),
+ .ei_array = NULL,
+ },
+ {
+ .data_type = QMI_EOTI,
+ .elem_len = 0,
+ .elem_size = 0,
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x00,
+ .offset = 0,
+ .ei_array = NULL,
+ },
+};
+
+static struct elem_info slimbus_power_resp_msg_v01_ei[] = {
+ {
+ .data_type = QMI_STRUCT,
+ .elem_len = 1,
+ .elem_size = sizeof(struct qmi_response_type_v01),
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x02,
+ .offset = offsetof(struct slimbus_power_resp_msg_v01, resp),
+ .ei_array = get_qmi_response_type_v01_ei(),
+ },
+ {
+ .data_type = QMI_EOTI,
+ .elem_len = 0,
+ .elem_size = 0,
+ .is_array = NO_ARRAY,
+ .tlv_type = 0x00,
+ .offset = 0,
+ .ei_array = NULL,
+ },
+};
+
+static void msm_slim_qmi_recv_msg(struct kthread_work *work)
+{
+ int rc;
+ struct msm_slim_qmi *qmi =
+ container_of(work, struct msm_slim_qmi, kwork);
+
+ rc = qmi_recv_msg(qmi->handle);
+ if (rc < 0)
+ pr_err("%s: Error receiving QMI message\n", __func__);
+}
+
+static void msm_slim_qmi_notify(struct qmi_handle *handle,
+ enum qmi_event_type event, void *notify_priv)
+{
+ struct msm_slim_ctrl *dev = notify_priv;
+ struct msm_slim_qmi *qmi = &dev->qmi;
+
+ switch (event) {
+ case QMI_RECV_MSG:
+ queue_kthread_work(&qmi->kworker, &qmi->kwork);
+ break;
+ default:
+ break;
+ }
+}
+
+static const char *get_qmi_error(struct qmi_response_type_v01 *r)
+{
+ if (r->result == QMI_RESULT_SUCCESS_V01 || r->error == QMI_ERR_NONE_V01)
+ return "No Error";
+ else if (r->error == QMI_ERR_NO_MEMORY_V01)
+ return "Out of Memory";
+ else if (r->error == QMI_ERR_INTERNAL_V01)
+ return "Unexpected error occurred";
+ else if (r->error == QMI_ERR_INCOMPATIBLE_STATE_V01)
+ return "Slimbus s/w already configured to a different mode";
+ else if (r->error == QMI_ERR_INVALID_ID_V01)
+ return "Slimbus hardware instance is not valid";
+ else
+ return "Unknown error";
+}
+
+static int msm_slim_qmi_send_select_inst_req(struct msm_slim_ctrl *dev,
+ struct slimbus_select_inst_req_msg_v01 *req)
+{
+ struct slimbus_select_inst_resp_msg_v01 resp = { { 0, 0 } };
+ struct msg_desc req_desc, resp_desc;
+ int rc;
+
+ req_desc.msg_id = SLIMBUS_QMI_SELECT_INSTANCE_REQ_V01;
+ req_desc.max_msg_len = sizeof(*req);
+ req_desc.ei_array = slimbus_select_inst_req_msg_v01_ei;
+
+ resp_desc.msg_id = SLIMBUS_QMI_SELECT_INSTANCE_RESP_V01;
+ resp_desc.max_msg_len = sizeof(resp);
+ resp_desc.ei_array = slimbus_select_inst_resp_msg_v01_ei;
+
+ rc = qmi_send_req_wait(dev->qmi.handle, &req_desc, req, sizeof(*req),
+ &resp_desc, &resp, sizeof(resp), 5000);
+ if (rc < 0) {
+ pr_err("%s: QMI send req failed %d\n", __func__, rc);
+ return rc;
+ }
+
+ /* Check the response */
+ if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
+ pr_err("%s: QMI request failed 0x%x (%s)\n", __func__,
+ resp.resp.result, get_qmi_error(&resp.resp));
+ return -EREMOTEIO;
+ }
+
+ return 0;
+}
+
+static int msm_slim_qmi_send_power_request(struct msm_slim_ctrl *dev,
+ struct slimbus_power_req_msg_v01 *req)
+{
+ struct slimbus_power_resp_msg_v01 resp = { { 0, 0 } };
+ struct msg_desc req_desc, resp_desc;
+ int rc;
+
+ req_desc.msg_id = SLIMBUS_QMI_POWER_REQ_V01;
+ req_desc.max_msg_len = sizeof(*req);
+ req_desc.ei_array = slimbus_power_req_msg_v01_ei;
+
+ resp_desc.msg_id = SLIMBUS_QMI_POWER_RESP_V01;
+ resp_desc.max_msg_len = sizeof(resp);
+ resp_desc.ei_array = slimbus_power_resp_msg_v01_ei;
+
+ rc = qmi_send_req_wait(dev->qmi.handle, &req_desc, req, sizeof(*req),
+ &resp_desc, &resp, sizeof(resp), 5000);
+ if (rc < 0) {
+ pr_err("%s: QMI send req failed %d\n", __func__, rc);
+ return rc;
+ }
+
+ /* Check the response */
+ if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
+ pr_err("%s: QMI request failed 0x%x (%s)\n", __func__,
+ resp.resp.result, get_qmi_error(&resp.resp));
+ return -EREMOTEIO;
+ }
+
+ return 0;
+}
+
+int msm_slim_qmi_init(struct msm_slim_ctrl *dev, bool apps_is_master)
+{
+ int rc = 0;
+ struct qmi_handle *handle;
+ struct slimbus_select_inst_req_msg_v01 req;
+
+ init_kthread_worker(&dev->qmi.kworker);
+
+ dev->qmi.task = kthread_run(kthread_worker_fn,
+ &dev->qmi.kworker, "msm_slim_qmi_clnt%d", dev->ctrl.nr);
+
+ if (IS_ERR(dev->qmi.task)) {
+ pr_err("%s: Failed to create QMI client kthread\n", __func__);
+ return -ENOMEM;
+ }
+
+ init_kthread_work(&dev->qmi.kwork, msm_slim_qmi_recv_msg);
+
+ handle = qmi_handle_create(msm_slim_qmi_notify, dev);
+ if (!handle) {
+ rc = -ENOMEM;
+ pr_err("%s: QMI client handle alloc failed\n", __func__);
+ goto qmi_handle_create_failed;
+ }
+
+ rc = qmi_connect_to_service(handle, SLIMBUS_QMI_SVC_ID,
+ SLIMBUS_QMI_INS_ID);
+ if (rc < 0) {
+ pr_err("%s: QMI server not found\n", __func__);
+ goto qmi_connect_to_service_failed;
+ }
+
+ /* Instance is 0 based */
+ req.instance = dev->ctrl.nr - 1;
+ req.mode_valid = 1;
+
+ /* Mode indicates the role of the ADSP */
+ if (apps_is_master)
+ req.mode = SLIMBUS_MODE_SATELLITE_V01;
+ else
+ req.mode = SLIMBUS_MODE_MASTER_V01;
+
+ dev->qmi.handle = handle;
+
+ rc = msm_slim_qmi_send_select_inst_req(dev, &req);
+ if (rc) {
+ pr_err("%s: failed to select h/w instance\n", __func__);
+ goto qmi_select_instance_failed;
+ }
+
+ return 0;
+
+qmi_select_instance_failed:
+ dev->qmi.handle = NULL;
+qmi_connect_to_service_failed:
+ qmi_handle_destroy(handle);
+qmi_handle_create_failed:
+ flush_kthread_worker(&dev->qmi.kworker);
+ kthread_stop(dev->qmi.task);
+ dev->qmi.task = NULL;
+ return rc;
+}
+
+void msm_slim_qmi_exit(struct msm_slim_ctrl *dev)
+{
+ qmi_handle_destroy(dev->qmi.handle);
+ flush_kthread_worker(&dev->qmi.kworker);
+ kthread_stop(dev->qmi.task);
+ dev->qmi.task = NULL;
+ dev->qmi.handle = NULL;
+}
+
+int msm_slim_qmi_power_request(struct msm_slim_ctrl *dev, bool active)
+{
+ struct slimbus_power_req_msg_v01 req;
+
+ if (active)
+ req.pm_req = SLIMBUS_PM_ACTIVE_V01;
+ else
+ req.pm_req = SLIMBUS_PM_INACTIVE_V01;
+
+ return msm_slim_qmi_send_power_request(dev, &req);
+}
diff --git a/drivers/slimbus/slim-msm.h b/drivers/slimbus/slim-msm.h
index 7d50620..7548478 100644
--- a/drivers/slimbus/slim-msm.h
+++ b/drivers/slimbus/slim-msm.h
@@ -12,6 +12,10 @@
#ifndef _SLIM_MSM_H
#define _SLIM_MSM_H
+
+#include <linux/kthread.h>
+#include <mach/msm_qmi_interface.h>
+
/* Per spec.max 40 bytes per received message */
#define SLIM_RX_MSGQ_BUF_LEN 40
@@ -64,6 +68,10 @@
#define MSM_MAX_NSATS 2
#define MSM_MAX_SATCH 32
+/* Slimbus QMI service */
+#define SLIMBUS_QMI_SVC_ID 0x0301
+#define SLIMBUS_QMI_INS_ID 1
+
#define PGD_THIS_EE(r, v) ((v) ? PGD_THIS_EE_V2(r) : PGD_THIS_EE_V1(r))
#define PGD_PORT(r, p, v) ((v) ? PGD_PORT_V2(r, p) : PGD_PORT_V1(r, p))
#define CFG_PORT(r, v) ((v) ? CFG_PORT_V2(r) : CFG_PORT_V1(r))
@@ -161,6 +169,13 @@
bool connected;
};
+struct msm_slim_qmi {
+ struct qmi_handle *handle;
+ struct task_struct *task;
+ struct kthread_work kwork;
+ struct kthread_worker kworker;
+};
+
struct msm_slim_ctrl {
struct slim_controller ctrl;
struct slim_framer framer;
@@ -197,6 +212,7 @@
enum msm_ctrl_state state;
int nsats;
u32 ver;
+ struct msm_slim_qmi qmi;
};
struct msm_sat_chan {
@@ -249,4 +265,8 @@
int msm_slim_sps_init(struct msm_slim_ctrl *dev, struct resource *bam_mem,
u32 pipe_reg, bool remote);
void msm_slim_sps_exit(struct msm_slim_ctrl *dev);
+
+void msm_slim_qmi_exit(struct msm_slim_ctrl *dev);
+int msm_slim_qmi_init(struct msm_slim_ctrl *dev, bool apps_is_master);
+int msm_slim_qmi_power_request(struct msm_slim_ctrl *dev, bool active);
#endif