linux/drivers/soc/qcom/smd-rpm.c
<<
>>
Prefs
   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/soc/qcom/smd.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 */
  34struct qcom_smd_rpm {
  35        struct qcom_smd_channel *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 */
  48struct 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 */
  61struct 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 */
  78struct 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 */
 100int 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 = qcom_smd_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
 146out:
 147        kfree(pkt);
 148        mutex_unlock(&rpm->lock);
 149        return ret;
 150}
 151EXPORT_SYMBOL(qcom_rpm_smd_write);
 152
 153static int qcom_smd_rpm_callback(struct qcom_smd_channel *channel,
 154                                 const void *data,
 155                                 size_t count)
 156{
 157        const struct qcom_rpm_header *hdr = data;
 158        size_t hdr_length = le32_to_cpu(hdr->length);
 159        const struct qcom_rpm_message *msg;
 160        struct qcom_smd_rpm *rpm = qcom_smd_get_drvdata(channel);
 161        const u8 *buf = data + sizeof(struct qcom_rpm_header);
 162        const u8 *end = buf + hdr_length;
 163        char msgbuf[32];
 164        int status = 0;
 165        u32 len, msg_length;
 166
 167        if (le32_to_cpu(hdr->service_type) != RPM_SERVICE_TYPE_REQUEST ||
 168            hdr_length < sizeof(struct qcom_rpm_message)) {
 169                dev_err(rpm->dev, "invalid request\n");
 170                return 0;
 171        }
 172
 173        while (buf < end) {
 174                msg = (struct qcom_rpm_message *)buf;
 175                msg_length = le32_to_cpu(msg->length);
 176                switch (le32_to_cpu(msg->msg_type)) {
 177                case RPM_MSG_TYPE_MSG_ID:
 178                        break;
 179                case RPM_MSG_TYPE_ERR:
 180                        len = min_t(u32, ALIGN(msg_length, 4), sizeof(msgbuf));
 181                        memcpy_fromio(msgbuf, msg->message, len);
 182                        msgbuf[len - 1] = 0;
 183
 184                        if (!strcmp(msgbuf, "resource does not exist"))
 185                                status = -ENXIO;
 186                        else
 187                                status = -EINVAL;
 188                        break;
 189                }
 190
 191                buf = PTR_ALIGN(buf + 2 * sizeof(u32) + msg_length, 4);
 192        }
 193
 194        rpm->ack_status = status;
 195        complete(&rpm->ack);
 196        return 0;
 197}
 198
 199static int qcom_smd_rpm_probe(struct qcom_smd_device *sdev)
 200{
 201        struct qcom_smd_rpm *rpm;
 202
 203        rpm = devm_kzalloc(&sdev->dev, sizeof(*rpm), GFP_KERNEL);
 204        if (!rpm)
 205                return -ENOMEM;
 206
 207        mutex_init(&rpm->lock);
 208        init_completion(&rpm->ack);
 209
 210        rpm->dev = &sdev->dev;
 211        rpm->rpm_channel = sdev->channel;
 212        qcom_smd_set_drvdata(sdev->channel, rpm);
 213
 214        dev_set_drvdata(&sdev->dev, rpm);
 215
 216        return of_platform_populate(sdev->dev.of_node, NULL, NULL, &sdev->dev);
 217}
 218
 219static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev)
 220{
 221        of_platform_depopulate(&sdev->dev);
 222}
 223
 224static 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        {}
 229};
 230MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
 231
 232static struct qcom_smd_driver qcom_smd_rpm_driver = {
 233        .probe = qcom_smd_rpm_probe,
 234        .remove = qcom_smd_rpm_remove,
 235        .callback = qcom_smd_rpm_callback,
 236        .driver  = {
 237                .name  = "qcom_smd_rpm",
 238                .owner = THIS_MODULE,
 239                .of_match_table = qcom_smd_rpm_of_match,
 240        },
 241};
 242
 243static int __init qcom_smd_rpm_init(void)
 244{
 245        return qcom_smd_driver_register(&qcom_smd_rpm_driver);
 246}
 247arch_initcall(qcom_smd_rpm_init);
 248
 249static void __exit qcom_smd_rpm_exit(void)
 250{
 251        qcom_smd_driver_unregister(&qcom_smd_rpm_driver);
 252}
 253module_exit(qcom_smd_rpm_exit);
 254
 255MODULE_AUTHOR("Bjorn Andersson <bjorn.andersson@sonymobile.com>");
 256MODULE_DESCRIPTION("Qualcomm SMD backed RPM driver");
 257MODULE_LICENSE("GPL v2");
 258