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 21 #include <linux/soc/qcom/smd.h> 22 #include <linux/soc/qcom/smd-rpm.h> 23 24 #define RPM_REQUEST_TIMEOUT (5 * HZ) 25 26 /** 27 * struct qcom_smd_rpm - state of the rpm device driver 28 * @rpm_channel: reference to the smd channel 29 * @ack: completion for acks 30 * @lock: mutual exclusion around the send/complete pair 31 * @ack_status: result of the rpm request 32 */ 33 struct qcom_smd_rpm { 34 struct qcom_smd_channel *rpm_channel; 35 36 struct completion ack; 37 struct mutex lock; 38 int ack_status; 39 }; 40 41 /** 42 * struct qcom_rpm_header - header for all rpm requests and responses 43 * @service_type: identifier of the service 44 * @length: length of the payload 45 */ 46 struct qcom_rpm_header { 47 u32 service_type; 48 u32 length; 49 }; 50 51 /** 52 * struct qcom_rpm_request - request message to the rpm 53 * @msg_id: identifier of the outgoing message 54 * @flags: active/sleep state flags 55 * @type: resource type 56 * @id: resource id 57 * @data_len: length of the payload following this header 58 */ 59 struct qcom_rpm_request { 60 u32 msg_id; 61 u32 flags; 62 u32 type; 63 u32 id; 64 u32 data_len; 65 }; 66 67 /** 68 * struct qcom_rpm_message - response message from the rpm 69 * @msg_type: indicator of the type of message 70 * @length: the size of this message, including the message header 71 * @msg_id: message id 72 * @message: textual message from the rpm 73 * 74 * Multiple of these messages can be stacked in an rpm message. 75 */ 76 struct qcom_rpm_message { 77 u32 msg_type; 78 u32 length; 79 union { 80 u32 msg_id; 81 u8 message[0]; 82 }; 83 }; 84 85 #define RPM_SERVICE_TYPE_REQUEST 0x00716572 /* "req\0" */ 86 87 #define RPM_MSG_TYPE_ERR 0x00727265 /* "err\0" */ 88 #define RPM_MSG_TYPE_MSG_ID 0x2367736d /* "msg#" */ 89 90 /** 91 * qcom_rpm_smd_write - write @buf to @type:@id 92 * @rpm: rpm handle 93 * @type: resource type 94 * @id: resource identifier 95 * @buf: the data to be written 96 * @count: number of bytes in @buf 97 */ 98 int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm, 99 int state, 100 u32 type, u32 id, 101 void *buf, 102 size_t count) 103 { 104 static unsigned msg_id = 1; 105 int left; 106 int ret; 107 108 struct { 109 struct qcom_rpm_header hdr; 110 struct qcom_rpm_request req; 111 u8 payload[count]; 112 } pkt; 113 114 /* SMD packets to the RPM may not exceed 256 bytes */ 115 if (WARN_ON(sizeof(pkt) >= 256)) 116 return -EINVAL; 117 118 mutex_lock(&rpm->lock); 119 120 pkt.hdr.service_type = RPM_SERVICE_TYPE_REQUEST; 121 pkt.hdr.length = sizeof(struct qcom_rpm_request) + count; 122 123 pkt.req.msg_id = msg_id++; 124 pkt.req.flags = BIT(state); 125 pkt.req.type = type; 126 pkt.req.id = id; 127 pkt.req.data_len = count; 128 memcpy(pkt.payload, buf, count); 129 130 ret = qcom_smd_send(rpm->rpm_channel, &pkt, sizeof(pkt)); 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 mutex_unlock(&rpm->lock); 142 return ret; 143 } 144 EXPORT_SYMBOL(qcom_rpm_smd_write); 145 146 static int qcom_smd_rpm_callback(struct qcom_smd_device *qsdev, 147 const void *data, 148 size_t count) 149 { 150 const struct qcom_rpm_header *hdr = data; 151 const struct qcom_rpm_message *msg; 152 struct qcom_smd_rpm *rpm = dev_get_drvdata(&qsdev->dev); 153 const u8 *buf = data + sizeof(struct qcom_rpm_header); 154 const u8 *end = buf + hdr->length; 155 char msgbuf[32]; 156 int status = 0; 157 u32 len; 158 159 if (hdr->service_type != RPM_SERVICE_TYPE_REQUEST || 160 hdr->length < sizeof(struct qcom_rpm_message)) { 161 dev_err(&qsdev->dev, "invalid request\n"); 162 return 0; 163 } 164 165 while (buf < end) { 166 msg = (struct qcom_rpm_message *)buf; 167 switch (msg->msg_type) { 168 case RPM_MSG_TYPE_MSG_ID: 169 break; 170 case RPM_MSG_TYPE_ERR: 171 len = min_t(u32, ALIGN(msg->length, 4), sizeof(msgbuf)); 172 memcpy_fromio(msgbuf, msg->message, len); 173 msgbuf[len - 1] = 0; 174 175 if (!strcmp(msgbuf, "resource does not exist")) 176 status = -ENXIO; 177 else 178 status = -EINVAL; 179 break; 180 } 181 182 buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg->length, 4); 183 } 184 185 rpm->ack_status = status; 186 complete(&rpm->ack); 187 return 0; 188 } 189 190 static int qcom_smd_rpm_probe(struct qcom_smd_device *sdev) 191 { 192 struct qcom_smd_rpm *rpm; 193 194 rpm = devm_kzalloc(&sdev->dev, sizeof(*rpm), GFP_KERNEL); 195 if (!rpm) 196 return -ENOMEM; 197 198 mutex_init(&rpm->lock); 199 init_completion(&rpm->ack); 200 201 rpm->rpm_channel = sdev->channel; 202 203 dev_set_drvdata(&sdev->dev, rpm); 204 205 return of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev); 206 } 207 208 static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev) 209 { 210 of_platform_depopulate(&sdev->dev); 211 } 212 213 static const struct of_device_id qcom_smd_rpm_of_match[] = { 214 { .compatible = "qcom,rpm-msm8974" }, 215 {} 216 }; 217 MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match); 218 219 static struct qcom_smd_driver qcom_smd_rpm_driver = { 220 .probe = qcom_smd_rpm_probe, 221 .remove = qcom_smd_rpm_remove, 222 .callback = qcom_smd_rpm_callback, 223 .driver = { 224 .name = "qcom_smd_rpm", 225 .owner = THIS_MODULE, 226 .of_match_table = qcom_smd_rpm_of_match, 227 }, 228 }; 229 230 static int __init qcom_smd_rpm_init(void) 231 { 232 return qcom_smd_driver_register(&qcom_smd_rpm_driver); 233 } 234 arch_initcall(qcom_smd_rpm_init); 235 236 static void __exit qcom_smd_rpm_exit(void) 237 { 238 qcom_smd_driver_unregister(&qcom_smd_rpm_driver); 239 } 240 module_exit(qcom_smd_rpm_exit); 241 242 MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>"); 243 MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver"); 244 MODULE_LICENSE("GPL v2"); 245