linux/drivers/soc/qcom/smd-rpm.c
<<
>>
Prefs
   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 * @dev:                rpm device
  24 * @ack:                completion for acks
  25 * @lock:               mutual exclusion around the send/complete pair
  26 * @ack_status:         result of the rpm request
  27 */
  28struct qcom_smd_rpm {
  29        struct rpmsg_endpoint *rpm_channel;
  30        struct platform_device *icc;
  31        struct device *dev;
  32
  33        struct completion ack;
  34        struct mutex lock;
  35        int ack_status;
  36};
  37
  38/**
  39 * struct qcom_rpm_header - header for all rpm requests and responses
  40 * @service_type:       identifier of the service
  41 * @length:             length of the payload
  42 */
  43struct qcom_rpm_header {
  44        __le32 service_type;
  45        __le32 length;
  46};
  47
  48/**
  49 * struct qcom_rpm_request - request message to the rpm
  50 * @msg_id:     identifier of the outgoing message
  51 * @flags:      active/sleep state flags
  52 * @type:       resource type
  53 * @id:         resource id
  54 * @data_len:   length of the payload following this header
  55 */
  56struct qcom_rpm_request {
  57        __le32 msg_id;
  58        __le32 flags;
  59        __le32 type;
  60        __le32 id;
  61        __le32 data_len;
  62};
  63
  64/**
  65 * struct qcom_rpm_message - response message from the rpm
  66 * @msg_type:   indicator of the type of message
  67 * @length:     the size of this message, including the message header
  68 * @msg_id:     message id
  69 * @message:    textual message from the rpm
  70 *
  71 * Multiple of these messages can be stacked in an rpm message.
  72 */
  73struct qcom_rpm_message {
  74        __le32 msg_type;
  75        __le32 length;
  76        union {
  77                __le32 msg_id;
  78                u8 message[0];
  79        };
  80};
  81
  82#define RPM_SERVICE_TYPE_REQUEST        0x00716572 /* "req\0" */
  83
  84#define RPM_MSG_TYPE_ERR                0x00727265 /* "err\0" */
  85#define RPM_MSG_TYPE_MSG_ID             0x2367736d /* "msg#" */
  86
  87/**
  88 * qcom_rpm_smd_write - write @buf to @type:@id
  89 * @rpm:        rpm handle
  90 * @state:      active/sleep state flags
  91 * @type:       resource type
  92 * @id:         resource identifier
  93 * @buf:        the data to be written
  94 * @count:      number of bytes in @buf
  95 */
  96int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm,
  97                       int state,
  98                       u32 type, u32 id,
  99                       void *buf,
 100                       size_t count)
 101{
 102        static unsigned msg_id = 1;
 103        int left;
 104        int ret;
 105        struct {
 106                struct qcom_rpm_header hdr;
 107                struct qcom_rpm_request req;
 108                u8 payload[];
 109        } *pkt;
 110        size_t size = sizeof(*pkt) + count;
 111
 112        /* SMD packets to the RPM may not exceed 256 bytes */
 113        if (WARN_ON(size >= 256))
 114                return -EINVAL;
 115
 116        pkt = kmalloc(size, GFP_KERNEL);
 117        if (!pkt)
 118                return -ENOMEM;
 119
 120        mutex_lock(&rpm->lock);
 121
 122        pkt->hdr.service_type = cpu_to_le32(RPM_SERVICE_TYPE_REQUEST);
 123        pkt->hdr.length = cpu_to_le32(sizeof(struct qcom_rpm_request) + count);
 124
 125        pkt->req.msg_id = cpu_to_le32(msg_id++);
 126        pkt->req.flags = cpu_to_le32(state);
 127        pkt->req.type = cpu_to_le32(type);
 128        pkt->req.id = cpu_to_le32(id);
 129        pkt->req.data_len = cpu_to_le32(count);
 130        memcpy(pkt->payload, buf, count);
 131
 132        ret = rpmsg_send(rpm->rpm_channel, pkt, size);
 133        if (ret)
 134                goto out;
 135
 136        left = wait_for_completion_timeout(&rpm->ack, RPM_REQUEST_TIMEOUT);
 137        if (!left)
 138                ret = -ETIMEDOUT;
 139        else
 140                ret = rpm->ack_status;
 141
 142out:
 143        kfree(pkt);
 144        mutex_unlock(&rpm->lock);
 145        return ret;
 146}
 147EXPORT_SYMBOL(qcom_rpm_smd_write);
 148
 149static int qcom_smd_rpm_callback(struct rpmsg_device *rpdev,
 150                                 void *data,
 151                                 int count,
 152                                 void *priv,
 153                                 u32 addr)
 154{
 155        const struct qcom_rpm_header *hdr = data;
 156        size_t hdr_length = le32_to_cpu(hdr->length);
 157        const struct qcom_rpm_message *msg;
 158        struct qcom_smd_rpm *rpm = dev_get_drvdata(&rpdev->dev);
 159        const u8 *buf = data + sizeof(struct qcom_rpm_header);
 160        const u8 *end = buf + hdr_length;
 161        char msgbuf[32];
 162        int status = 0;
 163        u32 len, msg_length;
 164
 165        if (le32_to_cpu(hdr->service_type) != RPM_SERVICE_TYPE_REQUEST ||
 166            hdr_length < sizeof(struct qcom_rpm_message)) {
 167                dev_err(rpm->dev, "invalid request\n");
 168                return 0;
 169        }
 170
 171        while (buf < end) {
 172                msg = (struct qcom_rpm_message *)buf;
 173                msg_length = le32_to_cpu(msg->length);
 174                switch (le32_to_cpu(msg->msg_type)) {
 175                case RPM_MSG_TYPE_MSG_ID:
 176                        break;
 177                case RPM_MSG_TYPE_ERR:
 178                        len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf));
 179                        memcpy_fromio(msgbuf, msg->message, len);
 180                        msgbuf[len - 1] = 0;
 181
 182                        if (!strcmp(msgbuf, "resource does not exist"))
 183                                status = -ENXIO;
 184                        else
 185                                status = -EINVAL;
 186                        break;
 187                }
 188
 189                buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4);
 190        }
 191
 192        rpm->ack_status = status;
 193        complete(&rpm->ack);
 194        return 0;
 195}
 196
 197static int qcom_smd_rpm_probe(struct rpmsg_device *rpdev)
 198{
 199        struct qcom_smd_rpm *rpm;
 200        int ret;
 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        rpm->icc = platform_device_register_data(&rpdev->dev, "icc_smd_rpm", -1,
 214                                                 NULL, 0);
 215        if (IS_ERR(rpm->icc))
 216                return PTR_ERR(rpm->icc);
 217
 218        ret = of_platform_populate(rpdev->dev.of_node, NULL, NULL, &rpdev->dev);
 219        if (ret)
 220                platform_device_unregister(rpm->icc);
 221
 222        return ret;
 223}
 224
 225static void qcom_smd_rpm_remove(struct rpmsg_device *rpdev)
 226{
 227        struct qcom_smd_rpm *rpm = dev_get_drvdata(&rpdev->dev);
 228
 229        platform_device_unregister(rpm->icc);
 230        of_platform_depopulate(&rpdev->dev);
 231}
 232
 233static const struct of_device_id qcom_smd_rpm_of_match[] = {
 234        { .compatible = "qcom,rpm-apq8084" },
 235        { .compatible = "qcom,rpm-ipq6018" },
 236        { .compatible = "qcom,rpm-msm8226" },
 237        { .compatible = "qcom,rpm-msm8916" },
 238        { .compatible = "qcom,rpm-msm8936" },
 239        { .compatible = "qcom,rpm-msm8974" },
 240        { .compatible = "qcom,rpm-msm8976" },
 241        { .compatible = "qcom,rpm-msm8994" },
 242        { .compatible = "qcom,rpm-msm8996" },
 243        { .compatible = "qcom,rpm-msm8998" },
 244        { .compatible = "qcom,rpm-sdm660" },
 245        { .compatible = "qcom,rpm-sm6115" },
 246        { .compatible = "qcom,rpm-sm6125" },
 247        { .compatible = "qcom,rpm-qcs404" },
 248        {}
 249};
 250MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
 251
 252static struct rpmsg_driver qcom_smd_rpm_driver = {
 253        .probe = qcom_smd_rpm_probe,
 254        .remove = qcom_smd_rpm_remove,
 255        .callback = qcom_smd_rpm_callback,
 256        .drv  = {
 257                .name  = "qcom_smd_rpm",
 258                .of_match_table = qcom_smd_rpm_of_match,
 259        },
 260};
 261
 262static int __init qcom_smd_rpm_init(void)
 263{
 264        return register_rpmsg_driver(&qcom_smd_rpm_driver);
 265}
 266arch_initcall(qcom_smd_rpm_init);
 267
 268static void __exit qcom_smd_rpm_exit(void)
 269{
 270        unregister_rpmsg_driver(&qcom_smd_rpm_driver);
 271}
 272module_exit(qcom_smd_rpm_exit);
 273
 274MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
 275MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");
 276MODULE_LICENSE("GPL v2");
 277