1 /* 2 * Copyright (c) 2015, Sony Mobile Communications AB. 3 * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License version 2 and 7 * only version 2 as published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 */ 14 15 #include <linux/module.h> 16 #include <linux/platform_device.h> 17 #include <linux/of_platform.h> 18 #include <linux/io.h> 19 #include <linux/interrupt.h> 20 #include <linux/slab.h> 21 22 #include <linux/rpmsg.h> 23 #include <linux/soc/qcom/smd-rpm.h> 24 25 #define RPM_REQUEST_TIMEOUT (5 * HZ) 26 27 /** 28 * struct qcom_smd_rpm - state of the rpm device driver 29 * @rpm_channel: reference to the smd channel 30 * @ack: completion for acks 31 * @lock: mutual exclusion around the send/complete pair 32 * @ack_status: result of the rpm request 33 */ 34 struct qcom_smd_rpm { 35 struct rpmsg_endpoint *rpm_channel; 36 struct device *dev; 37 38 struct completion ack; 39 struct mutex lock; 40 int ack_status; 41 }; 42 43 /** 44 * struct qcom_rpm_header - header for all rpm requests and responses 45 * @service_type: identifier of the service 46 * @length: length of the payload 47 */ 48 struct qcom_rpm_header { 49 __le32 service_type; 50 __le32 length; 51 }; 52 53 /** 54 * struct qcom_rpm_request - request message to the rpm 55 * @msg_id: identifier of the outgoing message 56 * @flags: active/sleep state flags 57 * @type: resource type 58 * @id: resource id 59 * @data_len: length of the payload following this header 60 */ 61 struct qcom_rpm_request { 62 __le32 msg_id; 63 __le32 flags; 64 __le32 type; 65 __le32 id; 66 __le32 data_len; 67 }; 68 69 /** 70 * struct qcom_rpm_message - response message from the rpm 71 * @msg_type: indicator of the type of message 72 * @length: the size of this message, including the message header 73 * @msg_id: message id 74 * @message: textual message from the rpm 75 * 76 * Multiple of these messages can be stacked in an rpm message. 77 */ 78 struct qcom_rpm_message { 79 __le32 msg_type; 80 __le32 length; 81 union { 82 __le32 msg_id; 83 u8 message[0]; 84 }; 85 }; 86 87 #define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */ 88 89 #define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */ 90 #define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */ 91 92 /** 93 * qcom_rpm_smd_write - write @buf to @type:@id 94 * @rpm: rpm handle 95 * @type: resource type 96 * @id: resource identifier 97 * @buf: the data to be written 98 * @count: number of bytes in @buf 99 */ 100 int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, 101 int state, 102 u32 type, u32 id, 103 void *buf, 104 size_t count) 105 { 106 static unsigned msg_id = 1; 107 int left; 108 int ret; 109 struct { 110 struct qcom_rpm_header hdr; 111 struct qcom_rpm_request req; 112 u8 payload[]; 113 } *pkt; 114 size_t size = sizeof(*pkt) + count; 115 116 /* SMD packets to the RPM may not exceed 256 bytes */ 117 if (WARN_ON(size >= 256)) 118 return -EINVAL; 119 120 pkt = kmalloc(size, GFP_KERNEL); 121 if (!pkt) 122 return -ENOMEM; 123 124 mutex_lock(&rpm->lock); 125 126 pkt->hdr.service_type = cpu_to_le32(RPM_SERVICE_TYPE_REQUEST); 127 pkt->hdr.length = cpu_to_le32(sizeof(struct qcom_rpm_request) + count); 128 129 pkt->req.msg_id = cpu_to_le32(msg_id++); 130 pkt->req.flags = cpu_to_le32(state); 131 pkt->req.type = cpu_to_le32(type); 132 pkt->req.id = cpu_to_le32(id); 133 pkt->req.data_len = cpu_to_le32(count); 134 memcpy(pkt->payload, buf, count); 135 136 ret = rpmsg_send(rpm->rpm_channel, pkt, size); 137 if (ret) 138 goto out; 139 140 left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT); 141 if (!left) 142 ret = -ETIMEDOUT; 143 else 144 ret = rpm->ack_status; 145 146 out: 147 kfree(pkt); 148 mutex_unlock(&rpm->lock); 149 return ret; 150 } 151 EXPORT_SYMBOL(qcom_rpm_smd_write); 152 153 static int qcom_smd_rpm_callback(struct rpmsg_device *rpdev, 154 void *data, 155 int count, 156 void *priv, 157 u32 addr) 158 { 159 const struct qcom_rpm_header *hdr = data; 160 size_t hdr_length = le32_to_cpu(hdr->length); 161 const struct qcom_rpm_message *msg; 162 struct qcom_smd_rpm *rpm = dev_get_drvdata(&rpdev->dev); 163 const u8 *buf = data + sizeof(struct qcom_rpm_header); 164 const u8 *end = buf + hdr_length; 165 char msgbuf[32]; 166 int status = 0; 167 u32 len, msg_length; 168 169 if (le32_to_cpu(hdr->service_type) != RPM_SERVICE_TYPE_REQUEST || 170 hdr_length < sizeof(struct qcom_rpm_message)) { 171 dev_err(rpm->dev, "invalid request\n"); 172 return 0; 173 } 174 175 while (buf < end) { 176 msg = (struct qcom_rpm_message *)buf; 177 msg_length = le32_to_cpu(msg->length); 178 switch (le32_to_cpu(msg->msg_type)) { 179 case RPM_MSG_TYPE_MSG_ID: 180 break; 181 case RPM_MSG_TYPE_ERR: 182 len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf)); 183 memcpy_fromio(msgbuf, msg->message, len); 184 msgbuf[len - 1] = 0; 185 186 if (!strcmp(msgbuf, "resource does not exist")) 187 status = -ENXIO; 188 else 189 status = -EINVAL; 190 break; 191 } 192 193 buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4); 194 } 195 196 rpm->ack_status = status; 197 complete(&rpm->ack); 198 return 0; 199 } 200 201 static int qcom_smd_rpm_probe(struct rpmsg_device *rpdev) 202 { 203 struct qcom_smd_rpm *rpm; 204 205 rpm = devm_kzalloc(&rpdev->dev, sizeof(*rpm), GFP_KERNEL); 206 if (!rpm) 207 return -ENOMEM; 208 209 mutex_init(&rpm->lock); 210 init_completion(&rpm->ack); 211 212 rpm->dev = &rpdev->dev; 213 rpm->rpm_channel = rpdev->ept; 214 dev_set_drvdata(&rpdev->dev, rpm); 215 216 return of_platform_populate(rpdev->dev.of_node, NULL, NULL, &rpdev->dev); 217 } 218 219 static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev) 220 { 221 of_platform_depopulate(&rpdev->dev); 222 } 223 224 static const struct of_device_id qcom_smd_rpm_of_match[] = { 225 { .compatible = "qcom,rpm-apq8084" }, 226 { .compatible = "qcom,rpm-msm8916" }, 227 { .compatible = "qcom,rpm-msm8974" }, 228 { .compatible = "qcom,rpm-msm8996" }, 229 {} 230 }; 231 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match); 232 233 static struct rpmsg_driver qcom_smd_rpm_driver = { 234 .probe = qcom_smd_rpm_probe, 235 .remove = qcom_smd_rpm_remove, 236 .callback = qcom_smd_rpm_callback, 237 .drv = { 238 .name = "qcom_smd_rpm", 239 .of_match_table = qcom_smd_rpm_of_match, 240 }, 241 }; 242 243 static int __init qcom_smd_rpm_init(void) 244 { 245 return register_rpmsg_driver(&qcom_smd_rpm_driver); 246 } 247 arch_initcall(qcom_smd_rpm_init); 248 249 static void __exit qcom_smd_rpm_exit(void) 250 { 251 unregister_rpmsg_driver(&qcom_smd_rpm_driver); 252 } 253 module_exit(qcom_smd_rpm_exit); 254 255 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 256 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver"); 257 MODULE_LICENSE("GPL v2"); 258