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 * @icc: interconnect proxy 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 platform_device *icc; 30 struct device *dev; 31 32 struct completion ack; 33 struct mutex lock; 34 int ack_status; 35 }; 36 37 /** 38 * struct qcom_rpm_header - header for all rpm requests and responses 39 * @service_type: identifier of the service 40 * @length: length of the payload 41 */ 42 struct qcom_rpm_header { 43 __le32 service_type; 44 __le32 length; 45 }; 46 47 /** 48 * struct qcom_rpm_request - request message to the rpm 49 * @msg_id: identifier of the outgoing message 50 * @flags: active/sleep state flags 51 * @type: resource type 52 * @id: resource id 53 * @data_len: length of the payload following this header 54 */ 55 struct qcom_rpm_request { 56 __le32 msg_id; 57 __le32 flags; 58 __le32 type; 59 __le32 id; 60 __le32 data_len; 61 }; 62 63 /** 64 * struct qcom_rpm_message - response message from the rpm 65 * @msg_type: indicator of the type of message 66 * @length: the size of this message, including the message header 67 * @msg_id: message id 68 * @message: textual message from the rpm 69 * 70 * Multiple of these messages can be stacked in an rpm message. 71 */ 72 struct qcom_rpm_message { 73 __le32 msg_type; 74 __le32 length; 75 union { 76 __le32 msg_id; 77 u8 message[0]; 78 }; 79 }; 80 81 #define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */ 82 83 #define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */ 84 #define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */ 85 86 /** 87 * qcom_rpm_smd_write - write @buf to @type:@id 88 * @rpm: rpm handle 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_KERNEL); 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 int ret; 199 200 rpm = devm_kzalloc(&rpdev->dev, sizeof(*rpm), GFP_KERNEL); 201 if (!rpm) 202 return -ENOMEM; 203 204 mutex_init(&rpm->lock); 205 init_completion(&rpm->ack); 206 207 rpm->dev = &rpdev->dev; 208 rpm->rpm_channel = rpdev->ept; 209 dev_set_drvdata(&rpdev->dev, rpm); 210 211 rpm->icc = platform_device_register_data(&rpdev->dev, "icc_smd_rpm", -1, 212 NULL, 0); 213 if (IS_ERR(rpm->icc)) 214 return PTR_ERR(rpm->icc); 215 216 ret = of_platform_populate(rpdev->dev.of_node, NULL, NULL, &rpdev->dev); 217 if (ret) 218 platform_device_unregister(rpm->icc); 219 220 return ret; 221 } 222 223 static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev) 224 { 225 struct qcom_smd_rpm *rpm = dev_get_drvdata(&rpdev->dev); 226 227 platform_device_unregister(rpm->icc); 228 of_platform_depopulate(&rpdev->dev); 229 } 230 231 static const struct of_device_id qcom_smd_rpm_of_match[] = { 232 { .compatible = "qcom,rpm-apq8084" }, 233 { .compatible = "qcom,rpm-msm8916" }, 234 { .compatible = "qcom,rpm-msm8974" }, 235 { .compatible = "qcom,rpm-msm8976" }, 236 { .compatible = "qcom,rpm-msm8996" }, 237 { .compatible = "qcom,rpm-msm8998" }, 238 { .compatible = "qcom,rpm-sdm660" }, 239 { .compatible = "qcom,rpm-qcs404" }, 240 {} 241 }; 242 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match); 243 244 static struct rpmsg_driver qcom_smd_rpm_driver = { 245 .probe = qcom_smd_rpm_probe, 246 .remove = qcom_smd_rpm_remove, 247 .callback = qcom_smd_rpm_callback, 248 .drv = { 249 .name = "qcom_smd_rpm", 250 .of_match_table = qcom_smd_rpm_of_match, 251 }, 252 }; 253 254 static int __init qcom_smd_rpm_init(void) 255 { 256 return register_rpmsg_driver(&qcom_smd_rpm_driver); 257 } 258 arch_initcall(qcom_smd_rpm_init); 259 260 static void __exit qcom_smd_rpm_exit(void) 261 { 262 unregister_rpmsg_driver(&qcom_smd_rpm_driver); 263 } 264 module_exit(qcom_smd_rpm_exit); 265 266 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 267 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver"); 268 MODULE_LICENSE("GPL v2"); 269