1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 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 driver 21 * @rpm_channel: reference to the smd channel 22 * @dev: rpm device 23 * @ack: completion for acks 24 * @lock: mutual exclusion around the send/complete pair 25 * @ack_status: result of the rpm request 26 */ 27 struct 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 responses 38 * @service_type: identifier of the service 39 * @length: length of the payload 40 */ 41 struct qcom_rpm_header { 42 __le32 service_type; 43 __le32 length; 44 }; 45 46 /** 47 * struct qcom_rpm_request - request message to the rpm 48 * @msg_id: identifier of the outgoing message 49 * @flags: active/sleep state flags 50 * @type: resource type 51 * @id: resource id 52 * @data_len: length of the payload following this header 53 */ 54 struct 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 rpm 64 * @msg_type: indicator of the type of message 65 * @length: the size of this message, including the message header 66 * @msg_id: message id 67 * @message: textual message from the rpm 68 * 69 * Multiple of these messages can be stacked in an rpm message. 70 */ 71 struct 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:@id 87 * @rpm: rpm handle 88 * @state: active/sleep state flags 89 * @type: resource type 90 * @id: resource identifier 91 * @buf: the data to be written 92 * @count: number of bytes in @buf 93 */ 94 int 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 else 138 ret = rpm->ack_status; 139 140 out: 141 kfree(pkt); 142 mutex_unlock(&rpm->lock); 143 return ret; 144 } 145 EXPORT_SYMBOL(qcom_rpm_smd_write); 146 147 static 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 else 183 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 195 static 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 213 static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev) 214 { 215 of_platform_depopulate(&rpdev->dev); 216 } 217 218 static const struct of_device_id qcom_smd_rpm_of_match[] = { 219 { .compatible = "qcom,rpm-apq8084" }, 220 { .compatible = "qcom,rpm-ipq6018" }, 221 { .compatible = "qcom,rpm-ipq9574" }, 222 { .compatible = "qcom,rpm-msm8226" }, 223 { .compatible = "qcom,rpm-msm8909" }, 224 { .compatible = "qcom,rpm-msm8916" }, 225 { .compatible = "qcom,rpm-msm8936" }, 226 { .compatible = "qcom,rpm-msm8953" }, 227 { .compatible = "qcom,rpm-msm8974" }, 228 { .compatible = "qcom,rpm-msm8976" }, 229 { .compatible = "qcom,rpm-msm8994" }, 230 { .compatible = "qcom,rpm-msm8996" }, 231 { .compatible = "qcom,rpm-msm8998" }, 232 { .compatible = "qcom,rpm-sdm660" }, 233 { .compatible = "qcom,rpm-sm6115" }, 234 { .compatible = "qcom,rpm-sm6125" }, 235 { .compatible = "qcom,rpm-sm6375" }, 236 { .compatible = "qcom,rpm-qcm2290" }, 237 { .compatible = "qcom,rpm-qcs404" }, 238 {} 239 }; 240 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match); 241 242 static struct rpmsg_driver qcom_smd_rpm_driver = { 243 .probe = qcom_smd_rpm_probe, 244 .remove = qcom_smd_rpm_remove, 245 .callback = qcom_smd_rpm_callback, 246 .drv = { 247 .name = "qcom_smd_rpm", 248 .of_match_table = qcom_smd_rpm_of_match, 249 }, 250 }; 251 252 static int __init qcom_smd_rpm_init(void) 253 { 254 return register_rpmsg_driver(&qcom_smd_rpm_driver); 255 } 256 arch_initcall(qcom_smd_rpm_init); 257 258 static void __exit qcom_smd_rpm_exit(void) 259 { 260 unregister_rpmsg_driver(&qcom_smd_rpm_driver); 261 } 262 module_exit(qcom_smd_rpm_exit); 263 264 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 265 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver"); 266 MODULE_LICENSE("GPL v2"); 267