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 if (!rpdev->dev.of_node) 200 return -EINVAL; 201 202 rpm = devm_kzalloc(&rpdev->dev, sizeof(*rpm), GFP_KERNEL); 203 if (!rpm) 204 return -ENOMEM; 205 206 mutex_init(&rpm->lock); 207 init_completion(&rpm->ack); 208 209 rpm->dev = &rpdev->dev; 210 rpm->rpm_channel = rpdev->ept; 211 dev_set_drvdata(&rpdev->dev, rpm); 212 213 return of_platform_populate(rpdev->dev.of_node, NULL, NULL, &rpdev->dev); 214 } 215 216 static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev) 217 { 218 of_platform_depopulate(&rpdev->dev); 219 } 220 221 static const struct rpmsg_device_id qcom_smd_rpm_id_table[] = { 222 { .name = "rpm_requests", }, 223 { /* sentinel */ } 224 }; 225 MODULE_DEVICE_TABLE(rpmsg, qcom_smd_rpm_id_table); 226 227 static struct rpmsg_driver qcom_smd_rpm_driver = { 228 .probe = qcom_smd_rpm_probe, 229 .remove = qcom_smd_rpm_remove, 230 .callback = qcom_smd_rpm_callback, 231 .id_table = qcom_smd_rpm_id_table, 232 .drv.name = "qcom_smd_rpm", 233 }; 234 235 static int __init qcom_smd_rpm_init(void) 236 { 237 return register_rpmsg_driver(&qcom_smd_rpm_driver); 238 } 239 arch_initcall(qcom_smd_rpm_init); 240 241 static void __exit qcom_smd_rpm_exit(void) 242 { 243 unregister_rpmsg_driver(&qcom_smd_rpm_driver); 244 } 245 module_exit(qcom_smd_rpm_exit); 246 247 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 248 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver"); 249 MODULE_LICENSE("GPL v2"); 250