273 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2015, Sony Mobile Communications AB.4 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.5 */6 7#include <linux/module.h>8#include <linux/platform_device.h>9#include <linux/of_platform.h>10#include <linux/io.h>11#include <linux/interrupt.h>12#include <linux/slab.h>13 14#include <linux/rpmsg.h>15#include <linux/soc/qcom/smd-rpm.h>16 17#define RPM_REQUEST_TIMEOUT (5 * HZ)18 19/**20 * struct qcom_smd_rpm - state of the rpm device driver21 * @rpm_channel: reference to the smd channel22 * @dev: rpm device23 * @ack: completion for acks24 * @lock: mutual exclusion around the send/complete pair25 * @ack_status: result of the rpm request26 */27struct qcom_smd_rpm {28 struct rpmsg_endpoint *rpm_channel;29 struct device *dev;30 31 struct completion ack;32 struct mutex lock;33 int ack_status;34};35 36/**37 * struct qcom_rpm_header - header for all rpm requests and responses38 * @service_type: identifier of the service39 * @length: length of the payload40 */41struct qcom_rpm_header {42 __le32 service_type;43 __le32 length;44};45 46/**47 * struct qcom_rpm_request - request message to the rpm48 * @msg_id: identifier of the outgoing message49 * @flags: active/sleep state flags50 * @type: resource type51 * @id: resource id52 * @data_len: length of the payload following this header53 */54struct qcom_rpm_request {55 __le32 msg_id;56 __le32 flags;57 __le32 type;58 __le32 id;59 __le32 data_len;60};61 62/**63 * struct qcom_rpm_message - response message from the rpm64 * @msg_type: indicator of the type of message65 * @length: the size of this message, including the message header66 * @msg_id: message id67 * @message: textual message from the rpm68 *69 * Multiple of these messages can be stacked in an rpm message.70 */71struct qcom_rpm_message {72 __le32 msg_type;73 __le32 length;74 union {75 __le32 msg_id;76 DECLARE_FLEX_ARRAY(u8, message);77 };78};79 80#define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */81 82#define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */83#define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */84 85/**86 * qcom_rpm_smd_write - write @buf to @type:@id87 * @rpm: rpm handle88 * @state: active/sleep state flags89 * @type: resource type90 * @id: resource identifier91 * @buf: the data to be written92 * @count: number of bytes in @buf93 */94int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm,95 int state,96 u32 type, u32 id,97 void *buf,98 size_t count)99{100 static unsigned msg_id = 1;101 int left;102 int ret;103 struct {104 struct qcom_rpm_header hdr;105 struct qcom_rpm_request req;106 u8 payload[];107 } *pkt;108 size_t size = sizeof(*pkt) + count;109 110 /* SMD packets to the RPM may not exceed 256 bytes */111 if (WARN_ON(size >= 256))112 return -EINVAL;113 114 pkt = kmalloc(size, GFP_ATOMIC);115 if (!pkt)116 return -ENOMEM;117 118 mutex_lock(&rpm->lock);119 120 pkt->hdr.service_type = cpu_to_le32(RPM_SERVICE_TYPE_REQUEST);121 pkt->hdr.length = cpu_to_le32(sizeof(struct qcom_rpm_request) + count);122 123 pkt->req.msg_id = cpu_to_le32(msg_id++);124 pkt->req.flags = cpu_to_le32(state);125 pkt->req.type = cpu_to_le32(type);126 pkt->req.id = cpu_to_le32(id);127 pkt->req.data_len = cpu_to_le32(count);128 memcpy(pkt->payload, buf, count);129 130 ret = rpmsg_send(rpm->rpm_channel, pkt, size);131 if (ret)132 goto out;133 134 left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT);135 if (!left)136 ret = -ETIMEDOUT;137 else138 ret = rpm->ack_status;139 140out:141 kfree(pkt);142 mutex_unlock(&rpm->lock);143 return ret;144}145EXPORT_SYMBOL_GPL(qcom_rpm_smd_write);146 147static int qcom_smd_rpm_callback(struct rpmsg_device *rpdev,148 void *data,149 int count,150 void *priv,151 u32 addr)152{153 const struct qcom_rpm_header *hdr = data;154 size_t hdr_length = le32_to_cpu(hdr->length);155 const struct qcom_rpm_message *msg;156 struct qcom_smd_rpm *rpm = dev_get_drvdata(&rpdev->dev);157 const u8 *buf = data + sizeof(struct qcom_rpm_header);158 const u8 *end = buf + hdr_length;159 char msgbuf[32];160 int status = 0;161 u32 len, msg_length;162 163 if (le32_to_cpu(hdr->service_type) != RPM_SERVICE_TYPE_REQUEST ||164 hdr_length < sizeof(struct qcom_rpm_message)) {165 dev_err(rpm->dev, "invalid request\n");166 return 0;167 }168 169 while (buf < end) {170 msg = (struct qcom_rpm_message *)buf;171 msg_length = le32_to_cpu(msg->length);172 switch (le32_to_cpu(msg->msg_type)) {173 case RPM_MSG_TYPE_MSG_ID:174 break;175 case RPM_MSG_TYPE_ERR:176 len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf));177 memcpy_fromio(msgbuf, msg->message, len);178 msgbuf[len - 1] = 0;179 180 if (!strcmp(msgbuf, "resource does not exist"))181 status = -ENXIO;182 else183 status = -EINVAL;184 break;185 }186 187 buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4);188 }189 190 rpm->ack_status = status;191 complete(&rpm->ack);192 return 0;193}194 195static int qcom_smd_rpm_probe(struct rpmsg_device *rpdev)196{197 struct qcom_smd_rpm *rpm;198 199 rpm = devm_kzalloc(&rpdev->dev, sizeof(*rpm), GFP_KERNEL);200 if (!rpm)201 return -ENOMEM;202 203 mutex_init(&rpm->lock);204 init_completion(&rpm->ack);205 206 rpm->dev = &rpdev->dev;207 rpm->rpm_channel = rpdev->ept;208 dev_set_drvdata(&rpdev->dev, rpm);209 210 return of_platform_populate(rpdev->dev.of_node, NULL, NULL, &rpdev->dev);211}212 213static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev)214{215 of_platform_depopulate(&rpdev->dev);216}217 218static const struct of_device_id qcom_smd_rpm_of_match[] = {219 { .compatible = "qcom,glink-smd-rpm" },220 { .compatible = "qcom,smd-rpm" },221 /*222 * Don't add any more compatibles to the list, two previous entryes223 * should match all defined devices.224 */225 { .compatible = "qcom,rpm-apq8084" },226 { .compatible = "qcom,rpm-ipq6018" },227 { .compatible = "qcom,rpm-ipq9574" },228 { .compatible = "qcom,rpm-msm8226" },229 { .compatible = "qcom,rpm-msm8909" },230 { .compatible = "qcom,rpm-msm8916" },231 { .compatible = "qcom,rpm-msm8936" },232 { .compatible = "qcom,rpm-msm8953" },233 { .compatible = "qcom,rpm-msm8974" },234 { .compatible = "qcom,rpm-msm8976" },235 { .compatible = "qcom,rpm-msm8994" },236 { .compatible = "qcom,rpm-msm8996" },237 { .compatible = "qcom,rpm-msm8998" },238 { .compatible = "qcom,rpm-sdm660" },239 { .compatible = "qcom,rpm-sm6115" },240 { .compatible = "qcom,rpm-sm6125" },241 { .compatible = "qcom,rpm-sm6375" },242 { .compatible = "qcom,rpm-qcm2290" },243 { .compatible = "qcom,rpm-qcs404" },244 {}245};246MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);247 248static struct rpmsg_driver qcom_smd_rpm_driver = {249 .probe = qcom_smd_rpm_probe,250 .remove = qcom_smd_rpm_remove,251 .callback = qcom_smd_rpm_callback,252 .drv = {253 .name = "qcom_smd_rpm",254 .of_match_table = qcom_smd_rpm_of_match,255 },256};257 258static int __init qcom_smd_rpm_init(void)259{260 return register_rpmsg_driver(&qcom_smd_rpm_driver);261}262arch_initcall(qcom_smd_rpm_init);263 264static void __exit qcom_smd_rpm_exit(void)265{266 unregister_rpmsg_driver(&qcom_smd_rpm_driver);267}268module_exit(qcom_smd_rpm_exit);269 270MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");271MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");272MODULE_LICENSE("GPL v2");273