]> git.kernelconcepts.de Git - karo-tx-uboot.git/commitdiff
misc: add support for Qualcomm SMD protocol
authorLothar Waßmann <LW@KARO-electronics.de>
Mon, 13 Mar 2017 15:01:24 +0000 (16:01 +0100)
committerLothar Waßmann <LW@KARO-electronics.de>
Tue, 14 Mar 2017 16:38:40 +0000 (17:38 +0100)
arch/arm/mach-snapdragon/include/mach/qcom-smd.h [new file with mode: 0644]
drivers/misc/Kconfig
drivers/misc/Makefile
drivers/misc/qcom-smd.c [new file with mode: 0644]
drivers/power/pmic/Makefile
drivers/power/pmic/pmic-qcom-smd-rpm.c [new file with mode: 0644]
include/power/pmic-qcom-smd-rpm.h [new file with mode: 0644]

diff --git a/arch/arm/mach-snapdragon/include/mach/qcom-smd.h b/arch/arm/mach-snapdragon/include/mach/qcom-smd.h
new file mode 100644 (file)
index 0000000..f9c30c4
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2017, Lothar Waßmann <LW@KARO-electronics.de>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ *
+ * based on: platform/msm_shared/include/smd.h
+ * from Android Little Kernel: https://github.com/littlekernel/lk
+ *
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Fundation, Inc. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __QCOM_SMD_H
+#define __QCOM_SMD_H
+
+#define SMD_CHANNEL_ALLOC_MAX 2048
+#define SMD_CHANNEL_NAME_SIZE_MAX 20
+
+/* Stream related states */
+#define SMD_SS_CLOSED          0x0 /* Closed: must be 0 */
+#define SMD_SS_OPENING         0x1 /* Stream is opening */
+#define SMD_SS_OPENED          0x2 /* Stream is opened */
+#define SMD_SS_FLUSHING                0x3 /* Stream is flushing */
+#define SMD_SS_CLOSING         0x4 /* Stream is closing */
+#define SMD_SS_RESET           0x5 /* Stream is resetting */
+#define SMD_SS_RESET_OPENING   0x6 /* Stream reset on local */
+
+typedef enum {
+       SMD_APPS_RPM = 0x0F,
+} smd_channel_type;
+
+typedef struct {
+       uint32_t stream_state;
+       uint32_t DTR_DSR;
+       uint32_t CTS_RTS;
+       uint32_t CD;
+       uint32_t RI;
+       uint32_t data_written;
+       uint32_t data_read;
+       uint32_t state_updated;
+       uint32_t mask_recv_intr;
+       uint32_t read_index;
+       uint32_t write_index;
+} smd_shared_stream_info_type;
+
+/* Each port has 2 FIFOs, one per direction */
+typedef struct {
+       char name[SMD_CHANNEL_NAME_SIZE_MAX];
+       uint32_t cid;
+       uint32_t ctype;
+       uint32_t ref_count;
+} smd_channel_alloc_entry_t;
+
+typedef enum {
+       SMD_STREAMING_BUFFER,
+} smd_protocol_type;
+
+typedef struct {
+       smd_protocol_type protocol;
+       uint32_t port_id;
+       smd_channel_type ch_type;
+} smd_port_to_info_type;
+
+typedef struct {
+       uint32_t pkt_size;
+       uint32_t app_field;
+       uint32_t app_ptr;
+       uint32_t kind;
+       uint32_t priority;
+} smd_pkt_hdr;
+
+typedef struct {
+       smd_shared_stream_info_type ch0;
+       smd_shared_stream_info_type ch1;
+} smd_port_ctrl_info;
+
+typedef struct {
+       smd_channel_alloc_entry_t alloc_entry;
+       uint8_t *send_buf;
+       uint8_t *recv_buf;
+       uint32_t fifo_size;
+       smd_port_ctrl_info *port_info;
+       uint32_t current_state;
+} smd_channel_info_t;
+
+int smd_init(smd_channel_info_t *ch, uint32_t ch_type);
+void smd_uninit(smd_channel_info_t *ch);
+int smd_read(smd_channel_info_t *ch, uint32_t *len, int ch_type,
+       uint32_t *response);
+int smd_write(smd_channel_info_t *ch, const void *data, uint32_t len, int type);
+void smd_signal_read_complete(smd_channel_info_t *ch, uint32_t len);
+#endif /* __QCOM_SMD_H */
index c6a753d576b7f176655b08104f533fa3d391f817..5f8d705dbf043b4908fa2318a452564a0d070b96 100644 (file)
@@ -129,6 +129,14 @@ config PCA9551_I2C_ADDR
        help
          The I2C address of the PCA9551 LED controller.
 
+config QCOM_SMD
+       bool "Enable Qualcomm SMD driver for communication with PMIC devices"
+       select QCOM_SMEM
+
+config QCOM_SMD_RPM
+       bool "Resource Power Manager (RPM) over SMD"
+       depends on QCOM_SMD
+
 config QCOM_SMEM
        bool "Enable Qualcomm shared memory driver"
 
index 6287374a5499951de2edd423feae9fca019450a5..3c927378c902f04033dc3539264b619f3d17305c 100644 (file)
@@ -27,6 +27,7 @@ obj-$(CONFIG_NUVOTON_NCT6102D) += nuvoton_nct6102d.o
 obj-$(CONFIG_NS87308) += ns87308.o
 obj-$(CONFIG_PDSP188x) += pdsp188x.o
 obj-$(CONFIG_$(SPL_)PWRSEQ) += pwrseq-uclass.o
+obj-$(CONFIG_QCOM_SMD) += qcom-smd.o
 obj-$(CONFIG_QCOM_SMEM) += qcom-smem.o
 ifdef CONFIG_DM_I2C
 ifndef CONFIG_SPL_BUILD
diff --git a/drivers/misc/qcom-smd.c b/drivers/misc/qcom-smd.c
new file mode 100644 (file)
index 0000000..846505c
--- /dev/null
@@ -0,0 +1,673 @@
+/*
+ * Qualcomm Shared Memory driver
+ *
+ * Copyright (C) 2017, Lothar Waßmann <LW@KARO-electronics.de>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ *
+ * based on: platform/msm_shared/smd.c
+ * from Android Little Kernel: https://github.com/littlekernel/lk
+ *
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Fundation, Inc. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+#include <common.h>
+#include <console.h>
+#include <errno.h>
+#include <malloc.h>
+#include <asm/io.h>
+#include <mach/qcom-smd.h>
+#include <power/pmic-qcom-smd-rpm.h>
+
+#include "qcom-smem.h"
+
+#define SMD_CHANNEL_ACCESS_RETRY       1000000
+#define APCS_ALIAS0_IPC_INTERRUPT      ((void *)GICD_BASE + 0x11008)
+
+static smd_channel_alloc_entry_t *smd_channel_alloc_entry;
+
+static void smd_notify_rpm(smd_channel_info_t *ch);
+
+#ifdef DEBUG
+static inline void __smd_dump_state(smd_channel_info_t *ch)
+{
+       printf("   st DSR CTS CD RI dw dr su ma ri wi\n");
+       printf("TX@%p:\n", &ch->port_info->ch0);
+       printf("%u ", ch->port_info->ch0.stream_state);
+       printf(" %2u ", ch->port_info->ch0.DTR_DSR);
+       printf(" %2u ", ch->port_info->ch0.CTS_RTS);
+       printf("%2u ", ch->port_info->ch0.CD);
+       printf("%2u ", ch->port_info->ch0.RI);
+       printf("%2u ", ch->port_info->ch0.data_written);
+       printf("%2u ", ch->port_info->ch0.data_read);
+       printf("%2u ", ch->port_info->ch0.state_updated);
+       printf("%2u ", ch->port_info->ch0.mask_recv_intr);
+       printf("%2u ", ch->port_info->ch0.read_index);
+       printf("%2u ", ch->port_info->ch0.write_index);
+       printf("\n");
+
+       printf("RX@%p:\n", &ch->port_info->ch1);
+       printf("%u ", ch->port_info->ch1.stream_state);
+       printf(" %2u ", ch->port_info->ch1.DTR_DSR);
+       printf(" %2u ", ch->port_info->ch1.CTS_RTS);
+       printf("%2u ", ch->port_info->ch1.CD);
+       printf("%2u ", ch->port_info->ch1.RI);
+       printf("%2u ", ch->port_info->ch1.data_written);
+       printf("%2u ", ch->port_info->ch1.data_read);
+       printf("%2u ", ch->port_info->ch1.state_updated);
+       printf("%2u ", ch->port_info->ch1.mask_recv_intr);
+       printf("%2u ", ch->port_info->ch1.read_index);
+       printf("%2u ", ch->port_info->ch1.write_index);
+       printf("\n");
+}
+#define smd_dump_state(ch) __smd_dump_state(ch)
+
+static inline void __smd_set_var(uint32_t *var, int val,
+                       const char *ch, const char *elem,
+                       const char *fn, int ln)
+{
+       if (*var != val) {
+               debug("%s@%d: setting %s.%s %08x -> %08x\n", fn, ln,
+                       ch, elem, *var, val);
+               *var = val;
+       } else {
+               debug("%s@%d: %s.%s unchanged: %08x\n", fn, ln,
+                       ch, elem, *var);
+       }
+}
+#define smd_set_var(chan, var, val)    __smd_set_var(&ch->port_info->chan.var, val, \
+                                               #chan, #var, __func__, __LINE__)
+#else
+static inline void smd_dump_state(smd_channel_info_t *ch)
+{
+}
+
+#define smd_set_var(chan, var, val)    ch->port_info->chan.var = val
+#endif
+
+static void smd_write_state(smd_channel_info_t *ch, uint32_t state)
+{
+       if (state == SMD_SS_OPENED) {
+               smd_set_var(ch0, DTR_DSR, 1);
+               smd_set_var(ch0, CTS_RTS, 1);
+               smd_set_var(ch0, CD, 1);
+       } else {
+               smd_set_var(ch0, DTR_DSR, 0);
+               smd_set_var(ch0, CTS_RTS, 0);
+               smd_set_var(ch0, CD, 0);
+       }
+
+       smd_set_var(ch0, stream_state, state);
+}
+
+static void smd_state_update(smd_channel_info_t *ch, uint32_t flag)
+{
+       if (flag)
+               smd_set_var(ch0, state_updated, flag);
+       smd_dump_state(ch);
+       flush_dcache_range((unsigned long)ch->port_info, sizeof(*ch));
+}
+
+static void smd_set_state(smd_channel_info_t *ch, uint32_t state, uint32_t flag)
+{
+       uint32_t current_state;
+       uint32_t size;
+
+       if (!ch->port_info) {
+               ch->port_info = smem_get_alloc_entry(SMEM_SMD_BASE_ID +
+                                               ch->alloc_entry.cid,
+                                               &size);
+               if (!ch->port_info) {
+                       printf("Failed to get sram entry for CID: %u\n",
+                               ch->alloc_entry.cid);
+                       return;
+               }
+       }
+
+       current_state = ch->port_info->ch0.stream_state;
+       debug("%s@%d: state: %u -> %u\n", __func__, __LINE__,
+               current_state, state);
+
+       switch (state) {
+       case SMD_SS_CLOSED:
+               if (current_state == SMD_SS_OPENED) {
+                       smd_write_state(ch, SMD_SS_CLOSING);
+               } else {
+                       smd_write_state(ch, SMD_SS_CLOSED);
+               }
+               break;
+       case SMD_SS_OPENING:
+               if (current_state == SMD_SS_CLOSING || current_state == SMD_SS_CLOSED) {
+                       smd_write_state(ch, SMD_SS_OPENING);
+                       smd_set_var(ch1, read_index, 0);
+                       smd_set_var(ch0, write_index, 0);
+                       smd_set_var(ch0, mask_recv_intr, 0);
+               }
+               break;
+       case SMD_SS_OPENED:
+               if (current_state == SMD_SS_OPENING) {
+                       smd_write_state(ch, SMD_SS_OPENED);
+               }
+               break;
+       case SMD_SS_CLOSING:
+               if (current_state == SMD_SS_OPENED) {
+                       smd_write_state(ch, SMD_SS_CLOSING);
+               }
+               break;
+       case SMD_SS_FLUSHING:
+       case SMD_SS_RESET:
+       case SMD_SS_RESET_OPENING:
+       default:
+               break;
+       }
+
+       ch->current_state = state;
+
+       smd_state_update(ch, flag);
+}
+
+#define SMD_TIMEOUT_MS 10
+
+static void smd_irq_handler(smd_channel_info_t *ch)
+{
+       static int closed;
+
+       smd_dump_state(ch);
+       if (ch->current_state == SMD_SS_CLOSED) {
+               debug("%s@%d: remote state=%u\n", __func__, __LINE__,
+                       ch->port_info->ch1.stream_state);
+               if (!closed) {
+                       closed = 1;
+                       smd_set_var(ch0, mask_recv_intr, 1);
+                       smd_set_var(ch0, data_written, 0);
+                       smd_set_var(ch0, write_index, 0);
+                       smd_set_var(ch1, read_index, 0);
+                       smd_set_var(ch0, read_index, 0);
+                       smd_set_state(ch, SMD_SS_CLOSED, 1);
+                       smd_notify_rpm(ch);
+               } else {
+                       smd_set_var(ch1, stream_state, SMD_SS_OPENING);
+                       smd_set_var(ch1, read_index, 0);
+                       smd_set_var(ch1, write_index, 0);
+                       free(smd_channel_alloc_entry);
+                       smd_channel_alloc_entry = NULL;
+               }
+               return;
+       } else {
+               closed = 0;
+       }
+
+       debug("%s@%d: ch0: updated: %d state %u -> %u\n", __func__, __LINE__,
+               ch->port_info->ch0.state_updated,
+               ch->current_state, ch->port_info->ch0.stream_state);
+       debug("%s@%d: ch1: updated: %d state %u -> %u\n", __func__, __LINE__,
+               ch->port_info->ch1.state_updated,
+               ch->current_state, ch->port_info->ch1.stream_state);
+
+       if (ch->port_info->ch1.state_updated)
+               smd_set_var(ch1, state_updated, 0);
+
+       /* Should we have to use a do while and change states until we complete */
+       while (ch->current_state != ch->port_info->ch1.stream_state)
+               smd_set_state(ch, ch->port_info->ch1.stream_state, 0);
+
+       if (ch->current_state == SMD_SS_CLOSING) {
+               smd_set_state(ch, SMD_SS_CLOSED, 1);
+               smd_notify_rpm(ch);
+               udelay(1);
+               debug("SMD channel released\n");
+       }
+}
+
+static int __smd_check_irq(smd_channel_info_t *ch,
+                       const char *fn, int ln)
+{
+       int ret;
+       unsigned long irqpend;
+       unsigned long irqactive;
+
+       irqpend = readl(0x0b000298);
+       irqactive = readl(0x0b000318);
+       ret = ((irqpend | irqactive) & 0x100) != 0;
+       if (ret) {
+               debug("%s@%d: GIC IRQPEND: %08lx IRQACTIVE: %08lx\n",
+                       fn, ln, irqpend, irqactive);
+               smd_irq_handler(ch);
+               writel(0x100, 0x0b000298);
+       }
+       return ret;
+}
+#define smd_check_irq(ch)      __smd_check_irq(ch, __func__, __LINE__)
+
+static inline int wait_for_smd_irq(smd_channel_info_t *ch)
+{
+       int ret;
+       int first = 1;
+       unsigned long start = get_timer(0);
+       unsigned long elapsed = 0;
+
+       do {
+               ret = smd_check_irq(ch);
+               if (ret)
+                       break;
+               if (first) {
+                       debug("Waiting for SMD interrupt\n");
+                       first = 0;
+               }
+               if ((elapsed = get_timer(start)) > SMD_TIMEOUT_MS) {
+                       printf("Wait for SMD completion timed out\n");
+                       break;
+               }
+               udelay(1);
+       } while (1);
+       if (ret || smd_check_irq(ch)) {
+               debug("GIC IRQ arrived after %lu ticks\n", elapsed);
+               return 1;
+       }
+       return 0;
+}
+
+static void smd_notify_rpm(smd_channel_info_t *ch)
+{
+       smd_check_irq(ch);
+       /* Set BIT 0 to notify RPM via IPC interrupt*/
+       writel(BIT(0), APCS_ALIAS0_IPC_INTERRUPT);
+       smd_check_irq(ch);
+}
+
+static int smd_get_channel_entry(smd_channel_info_t *ch, uint32_t ch_type)
+{
+       int i;
+
+       for (i = 0; i < SMEM_NUM_SMD_STREAM_CHANNELS; i++) {
+               if ((smd_channel_alloc_entry[i].ctype & 0xFF) == ch_type) {
+                       memcpy(&ch->alloc_entry, &smd_channel_alloc_entry[i],
+                               sizeof(smd_channel_alloc_entry_t));
+                       return 1;
+               }
+       }
+       /* Channel not found, retry again */
+       printf("Channel not found, wait and retry for the update\n");
+       return 0;
+}
+
+static int smd_get_channel_info(smd_channel_info_t *ch, uint32_t ch_type)
+{
+       int ret;
+       uint8_t *fifo_buf;
+       uint32_t fifo_buf_size;
+       uint32_t size;
+
+       ret = smd_get_channel_entry(ch, ch_type);
+       if (!ret)
+               return ret;
+
+       ch->port_info = smem_get_alloc_entry(SMEM_SMD_BASE_ID + ch->alloc_entry.cid,
+                                       &size);
+       if (ch->port_info == NULL) {
+               printf("Failed to get smem entry type %u\n",
+                       SMEM_SMD_BASE_ID + ch->alloc_entry.cid);
+               return 0;
+       }
+
+       fifo_buf = smem_get_alloc_entry(SMEM_SMD_FIFO_BASE_ID + ch->alloc_entry.cid,
+                                       &fifo_buf_size);
+
+       fifo_buf_size /= 2;
+       ch->send_buf = fifo_buf;
+       ch->recv_buf = fifo_buf + fifo_buf_size;
+       ch->fifo_size = fifo_buf_size;
+
+       return 1;
+}
+
+static bool is_channel_open(smd_channel_info_t *ch)
+{
+       if (ch->port_info->ch0.stream_state == SMD_SS_OPENED &&
+               (ch->port_info->ch1.stream_state == SMD_SS_OPENED ||
+                       ch->port_info->ch1.stream_state == SMD_SS_FLUSHING))
+               return true;
+       else
+               return false;
+}
+
+static inline uint32_t *smd_fifo_entry(void *fifo, int index)
+{
+       return (uint32_t *)(fifo + index);
+}
+
+/* Copy the local buffer to fifo buffer.
+ * Takes care of fifo overlap.
+ * Uses the fifo as circular buffer, if the request data
+ * exceeds the max size of the buffer start from the beginning.
+ */
+static void memcpy_to_fifo(smd_channel_info_t *ch_ptr, const uint32_t *src,
+                       size_t len)
+{
+       uint32_t write_index = ch_ptr->port_info->ch0.write_index;
+       uint32_t *dest = smd_fifo_entry(ch_ptr->send_buf, write_index);
+
+       while (len) {
+               *dest++ = *src++;
+               len -= 4;
+               write_index += 4;
+               if (write_index >= ch_ptr->fifo_size) {
+                       write_index = 0;
+                       dest = smd_fifo_entry(ch_ptr->send_buf, write_index);
+               }
+       }
+       ch_ptr->port_info->ch0.write_index = write_index;
+}
+
+/* Copy the fifo buffer to a local destination.
+ * Takes care of fifo overlap.
+ * If the response data is split across with some part at
+ * end of fifo and some at the beginning of the fifo
+ */
+static void memcpy_from_fifo(smd_channel_info_t *ch_ptr, uint32_t *dest,
+                       size_t len)
+{
+       uint32_t read_index = ch_ptr->port_info->ch1.read_index;
+       uint32_t *src = smd_fifo_entry(ch_ptr->recv_buf, read_index);
+
+       while (len) {
+               if (dest)
+                       *dest++ = *src++;
+               else
+                       printf("Discarding %08x read from FIFO\n", *src++);
+               len -= 4;
+               read_index += 4;
+               if (read_index >= ch_ptr->fifo_size) {
+                       read_index = 0;
+                       src = smd_fifo_entry(ch_ptr->recv_buf, read_index);
+               }
+       }
+       ch_ptr->port_info->ch1.read_index = read_index;
+}
+
+struct rpm_message {
+       rpm_gen_hdr hdr;
+       union {
+               u8 message;
+               uint32_t val;
+       };
+};
+
+struct rpm_message_hdr {
+       rpm_gen_hdr hdr;
+       struct rpm_message msg;
+};
+
+void smd_parse_response(const void *buf, size_t size)
+{
+       const struct rpm_message_hdr *hdr = buf;
+       const struct rpm_message *msg = &hdr->msg;
+       size -= sizeof(rpm_gen_hdr);
+
+       switch (le32_to_cpu(hdr->hdr.type)) {
+       case 0x00716572:
+               while (size) {
+                       size_t len = ALIGN(msg->hdr.len, sizeof(uint32_t)) +
+                               sizeof(rpm_gen_hdr);
+
+                       if (len > size) {
+                               printf("Invalid RPM MSG length %zu received\n",
+                                       len);
+                               return;
+                       }
+
+                       switch (msg->hdr.type) {
+                       case 0x2367736d:
+                               break;
+                       case 0x00727265:
+                               printf("SMD ERR: %*s\n",
+                                       msg->hdr.len, &msg->message);
+                               break;
+                       default:
+                               printf("Unhandled RPM MSG type 0x%02x\n",
+                                       msg->hdr.type);
+                       }
+                       size -= len;
+                       msg = (void *)msg + len;
+               }
+               break;
+       default:
+               printf("Unsupported MSG type: %02x\n", hdr->hdr.type);
+       }
+}
+
+static inline int smd_read_avail(smd_channel_info_t *ch, uint32_t size)
+{
+       smd_shared_stream_info_type *ch1 = &ch->port_info->ch1;
+
+       invalidate_dcache_range((unsigned long)ch->port_info, size);
+       return ch1->write_index - ch1->read_index;
+}
+
+int smd_read(smd_channel_info_t *ch, uint32_t *len, int ch_type,
+       uint32_t *response)
+{
+       smd_pkt_hdr smd_hdr = {};
+       uint32_t size;
+       int timeout = 10000;
+
+       /* Read the indices from smem */
+       ch->port_info = smem_get_alloc_entry(SMEM_SMD_BASE_ID + ch->alloc_entry.cid,
+                                       &size);
+       if (!ch->port_info->ch1.DTR_DSR) {
+               printf("%s: DTR is off\n", __func__);
+               return -EBUSY;
+       }
+
+       /* Wait until the data size in the smd buffer is >= smd packet header size */
+       while (smd_read_avail(ch, size) < sizeof(smd_pkt_hdr)) {
+               if (--timeout < 0)
+                       return -ETIMEDOUT;
+               udelay(100);
+       }
+
+       /* Copy the smd buffer to local buf */
+       memcpy_from_fifo(ch, (uint32_t *)&smd_hdr, sizeof(smd_hdr));
+
+       invalidate_dcache_range((unsigned long)&smd_hdr, sizeof(smd_hdr));
+
+       /* Wait on the data being updated in SMEM before returning the response */
+       while ((ch->port_info->ch1.write_index - ch->port_info->ch1.read_index) <
+               smd_hdr.pkt_size) {
+               /* Get the update info from memory */
+               invalidate_dcache_range((unsigned long)ch->port_info, size);
+       }
+
+       /* We are good to return the response now */
+       if (*len < smd_hdr.pkt_size) {
+               size_t size = smd_hdr.pkt_size;
+               static char *msgbuf;
+               static size_t buflen;
+               void *buf;
+
+               debug("Input buffer too small (%u bytes) for response (%zu bytes)\n",
+                       *len, size);
+
+               *len = 0;
+               if (!msgbuf || buflen < size) {
+                       free(msgbuf);
+                       msgbuf = malloc(size);
+                       if (!msgbuf) {
+                               printf("Failed to allocate %zu bytes for SMD response buffer\n",
+                                       size);
+                               return -ENOMEM;
+                       }
+                       buflen = size;
+               }
+               buf = msgbuf;
+               memcpy_from_fifo(ch, buf, size);
+               smd_parse_response(buf, size);
+       } else {
+               memcpy_from_fifo(ch, response, smd_hdr.pkt_size);
+               *len = smd_hdr.pkt_size;
+       }
+       invalidate_dcache_range((unsigned long)response, *len);
+       debug("Read %u bytes from SMD\n", smd_hdr.pkt_size);
+       return 0;
+}
+
+void smd_signal_read_complete(smd_channel_info_t *ch, uint32_t len)
+{
+       /* Clear the data_written flag */
+       smd_set_var(ch1, data_written, 0);
+
+       /* Set the data_read flag */
+       smd_set_var(ch0, data_read, 1);
+
+       if (!ch->port_info->ch1.mask_recv_intr) {
+               dsb();
+               smd_notify_rpm(ch);
+       }
+}
+
+int smd_write(smd_channel_info_t *ch, const void *data, uint32_t len, int ch_type)
+{
+       smd_pkt_hdr smd_hdr = {};
+       uint32_t size;
+
+       if (len + sizeof(smd_hdr) > ch->fifo_size) {
+               printf("%s: len %lu is greater than fifo size (%u)\n",
+                       __func__, len + sizeof(smd_hdr), ch->fifo_size);
+               return -EMSGSIZE;
+       }
+
+       /* Read the indices from smem */
+       ch->port_info = smem_get_alloc_entry(SMEM_SMD_BASE_ID + ch->alloc_entry.cid,
+                                       &size);
+
+       if (!is_channel_open(ch)) {
+               printf("%s: channel is not in OPEN state \n", __func__);
+               return -EINVAL;
+       }
+
+       if (!ch->port_info->ch0.DTR_DSR) {
+               printf("%s: DTR is off\n", __func__);
+               return -EBUSY;
+       }
+
+       /* Clear the data_read flag */
+       smd_set_var(ch1, data_read, 0);
+
+       /* copy the local buf to smd buf */
+       smd_hdr.pkt_size = len;
+
+       memcpy_to_fifo(ch, (uint32_t *)&smd_hdr, sizeof(smd_hdr));
+
+       memcpy_to_fifo(ch, data, len);
+
+       dsb();
+
+       /* Set the necessary flags */
+
+       smd_set_var(ch0, data_written, 1);
+       smd_set_var(ch0, mask_recv_intr, 0);
+
+       dsb();
+
+       smd_notify_rpm(ch);
+
+       return 0;
+}
+
+int smd_init(smd_channel_info_t *ch, uint32_t ch_type)
+{
+       int ret;
+       int chnl_found;
+       int timeout = SMD_CHANNEL_ACCESS_RETRY;
+
+       writel(0x100, 0x0b000298);
+       smd_channel_alloc_entry = memalign(CONFIG_SYS_CACHELINE_SIZE,
+                                       SMD_CHANNEL_ALLOC_MAX);
+       if (smd_channel_alloc_entry == NULL) {
+               printf("Failed to allocate %u bytes for SMD channels\n",
+                       SMD_CHANNEL_ALLOC_MAX);
+               return -ENOMEM;
+       }
+
+       printf("Waiting for the RPM to populate smd channel table\n");
+
+       do {
+               ret = smem_read_alloc_entry(SMEM_CHANNEL_ALLOC_TBL,
+                                       smd_channel_alloc_entry,
+                                       SMD_CHANNEL_ALLOC_MAX);
+               if (ret) {
+                       printf("ERROR reading smem channel alloc tbl\n");
+                       return ret;
+               }
+
+               chnl_found = smd_get_channel_info(ch, ch_type);
+               timeout--;
+               udelay(10);
+       } while (timeout > 0 && !chnl_found);
+
+       if (timeout <= 0) {
+               printf("Apps timed out waiting for RPM-->APPS channel entry\n");
+               return -ETIMEDOUT;
+       }
+
+       smd_dump_state(ch);
+       smd_set_state(ch, SMD_SS_OPENING, 1);
+
+       smd_notify_rpm(ch);
+       wait_for_smd_irq(ch);
+       return 0;
+}
+
+void smd_uninit(smd_channel_info_t *ch)
+{
+       if (ch->current_state == SMD_SS_CLOSED)
+               return;
+
+       smd_set_state(ch, SMD_SS_CLOSING, 1);
+       smd_notify_rpm(ch);
+
+       while (ch->current_state != SMD_SS_CLOSED) {
+               wait_for_smd_irq(ch);
+               if (ctrlc())
+                       break;
+       }
+
+       if (!had_ctrlc()) {
+               /* wait until smem entry has been deallocated in smd_irq_handler() */
+               while (smd_channel_alloc_entry) {
+                       wait_for_smd_irq(ch);
+                       if (ctrlc())
+                               break;
+               }
+       }
+
+       smd_notify_rpm(ch);
+
+       if (had_ctrlc())
+               clear_ctrlc();
+}
index 52b4f711fbf1da6ac0cf81cdf84e5a240d130201..7ead298831d21a1131bf4330b28bed24ed728e99 100644 (file)
@@ -29,3 +29,4 @@ obj-$(CONFIG_POWER_TPS65218) += pmic_tps62362.o
 obj-$(CONFIG_POWER_TPS65218) += pmic_tps65218.o
 obj-$(CONFIG_POWER_TPS65910) += pmic_tps65910.o
 obj-$(CONFIG_POWER_HI6553) += pmic_hi6553.o
+obj-$(CONFIG_QCOM_SMD_RPM) += pmic-qcom-smd-rpm.o
diff --git a/drivers/power/pmic/pmic-qcom-smd-rpm.c b/drivers/power/pmic/pmic-qcom-smd-rpm.c
new file mode 100644 (file)
index 0000000..7509b35
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2017, Lothar Waßmann <LW@KARO-electronics.de>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ *
+ * based on: platform/msm_shared/rpm-smd.c
+ * from Android Little Kernel: https://github.com/littlekernel/lk
+ *
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Fundation, Inc. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <mach/qcom-smd.h>
+#include <power/pmic-qcom-smd-rpm.h>
+
+#define RPM_REQ_MAGIC 0x00716572
+#define RPM_CMD_MAGIC 0x00646d63
+#define REQ_MSG_LENGTH 0x14
+#define CMD_MSG_LENGTH 0x08
+#define ACK_MSG_LENGTH 0x0C
+
+static uint32_t msg_id;
+static smd_channel_info_t ch;
+
+void smd_rpm_init(void)
+{
+       smd_init(&ch, SMD_APPS_RPM);
+}
+
+void smd_rpm_uninit(void)
+{
+       smd_uninit(&ch);
+}
+
+static void fill_kvp_object(kvp_data **kdata, uint32_t *data, uint32_t len)
+{
+       *kdata = memalign(CONFIG_SYS_CACHELINE_SIZE,
+                       ALIGN(len, CONFIG_SYS_CACHELINE_SIZE));
+       if (!*kdata)
+               hang();
+
+       memcpy(*kdata, data, len);
+}
+
+static int rpm_read_ack(void)
+{
+       int ret;
+       rpm_ack_msg ack;
+       msg_type type;
+       uint32_t len = sizeof(ack);
+
+       ret = smd_read(&ch, &len, SMD_APPS_RPM, (void *)&ack);
+       if (ret)
+               return ret;
+       if (len == 0) {
+               printf("Failed to read ACK from RPM\n");
+               return -EIO;
+       }
+
+       invalidate_dcache_range((unsigned long)&ack, sizeof(ack.hdr));
+
+       if (ack.hdr.type == RPM_CMD_MAGIC) {
+               type = RPM_CMD_TYPE;
+       } else if (ack.hdr.type == RPM_REQ_MAGIC) {
+               type = RPM_REQUEST_TYPE;
+       } else {
+               printf("Invalid RPM Header type %02x\n",
+                       ack.hdr.type);
+               return -EINVAL;
+       }
+
+       if (type == RPM_CMD_TYPE && ack.hdr.len == ACK_MSG_LENGTH) {
+               debug("Received SUCCESS CMD ACK\n");
+       } else if (type == RPM_REQUEST_TYPE && ack.hdr.len == ACK_MSG_LENGTH) {
+               debug("Received SUCCESS REQ ACK\n");
+       } else {
+               printf("Received ERROR ACK: type %02x len %04x\n",
+                       type, ack.hdr.len);
+               return -ENXIO;
+       }
+       debug("Read %u bytes from SMD\n", len);
+       return 0;
+}
+
+int rpm_send_data(uint32_t *data, uint32_t len, msg_type type)
+{
+       rpm_req *req;
+       rpm_cmd cmd;
+       uint32_t len_to_smd;
+       int ret;
+
+       debug("%s@%d: sending RPM request %02x %p len %u\n",
+               __func__, __LINE__, type, data, len);
+
+       switch (type) {
+       case RPM_REQUEST_TYPE:
+               req = memalign(CONFIG_SYS_CACHELINE_SIZE,
+                       ALIGN(len - 8 + sizeof(*req),
+                               CONFIG_SYS_CACHELINE_SIZE));
+               if (req == NULL)
+                       return -ENOMEM;
+
+               req->hdr.type = RPM_REQ_MAGIC;
+               req->hdr.len = len + REQ_MSG_LENGTH;
+               req->req_hdr.id = ++msg_id;
+               req->req_hdr.set = 0;
+               req->req_hdr.resourceType = data[RESOURCETYPE];
+               req->req_hdr.resourceId = data[RESOURCEID];
+               req->req_hdr.dataLength = len;
+
+               len_to_smd = req->req_hdr.dataLength + sizeof(*req) -
+                       2 * sizeof(uint32_t);
+
+               memcpy(req->payload, &data[2], req->req_hdr.dataLength);
+
+               ret = smd_write(&ch, (void *)req, len_to_smd, SMD_APPS_RPM);
+               free(req);
+               if (ret) {
+                       printf("Failed to send RPM request\n");
+                       return ret;
+               }
+
+               /* Read the response */
+               ret = rpm_read_ack();
+               smd_signal_read_complete(&ch, 0);
+               break;
+
+       case RPM_CMD_TYPE:
+               cmd.hdr.type = RPM_CMD_MAGIC;
+               cmd.hdr.len = CMD_MSG_LENGTH;
+               len_to_smd = sizeof(rpm_cmd);
+
+               fill_kvp_object(&cmd.data, data, len);
+               ret = smd_write(&ch, (void *)&cmd, len_to_smd, SMD_APPS_RPM);
+               free(cmd.data);
+               if (ret)
+                       printf("Failed to send RPM request\n");
+               break;
+
+       default:
+               printf("Invalid RPM request type: %02x\n", type);
+               ret = -EINVAL;
+       }
+
+       return ret;
+}
+
+uint32_t rpm_recv_data(uint32_t *response, uint32_t *len)
+{
+       msg_type type;
+       rpm_ack_msg *m;
+       int ret = smd_read(&ch, len, SMD_APPS_RPM, response);
+
+       if (ret)
+               return 0;
+       if (*len == 0) {
+               printf("Failed to read data from RPM\n");
+               return 0;
+       }
+
+       invalidate_dcache_range((unsigned long)response, *len);
+
+       m = (void *)response;
+       if (m->hdr.type == RPM_CMD_MAGIC) {
+               type = RPM_CMD_TYPE;
+       } else if(m->hdr.type == RPM_REQ_MAGIC) {
+               type = RPM_REQUEST_TYPE;
+       } else {
+               printf("Invalid RPM Header type %02x\n",
+                       m->hdr.type);
+               return 0;
+       }
+
+       if (type == RPM_CMD_TYPE && m->hdr.len == ACK_MSG_LENGTH) {
+               debug("Received SUCCESS CMD ACK\n");
+       } else if (type == RPM_REQUEST_TYPE && m->hdr.len == ACK_MSG_LENGTH) {
+               debug("Received SUCCESS REQ ACK\n");
+       } else {
+               printf("Received ERROR ACK\n");
+               return 0;
+       }
+       debug("Read %u bytes from SMD\n", *len);
+       return *len;
+}
diff --git a/include/power/pmic-qcom-smd-rpm.h b/include/power/pmic-qcom-smd-rpm.h
new file mode 100644 (file)
index 0000000..8eb9aa8
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017, Lothar Waßmann <LW@KARO-electronics.de>
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ *
+ * based on: platform/msm_shared/include/rpm-smd.h
+ * from Android Little Kernel: https://github.com/littlekernel/lk
+ *
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Fundation, Inc. nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __PMIC_QCOM_SMD_RPM_H
+#define __PMIC_QCOM_SMD_RPM_H
+
+#define KEY_SOFTWARE_ENABLE            0x6E657773 // swen - software enable
+#define KEY_LDO_SOFTWARE_MODE          0X646D736C // lsmd - LDO software mode
+#define KEY_SMPS_SOFTWARE_MODE         0X646D7373 // ssmd - SMPS software mode
+#define KEY_PIN_CTRL_ENABLE            0x6E656370 //pcen - pin control enable
+#define KEY_PIN_CTRL_POWER_MODE                0x646d6370 // pcmd - pin control mode
+#define KEY_CURRENT                    0x616D //ma
+#define KEY_MICRO_VOLT                 0x7675 //uv
+#define KEY_FREQUENCY                  0x71657266 //freq
+#define KEY_FREQUENCY_REASON           0x6E736572 //resn
+#define KEY_FOLLOW_QUIET_MODE          0x6D71 //qm
+#define KEY_HEAD_ROOM                  0x7268 // hr
+#define KEY_PIN_CTRL_CLK_BUFFER_ENABLE_KEY 0x62636370 // pccb - clk buffer pin control
+#define KEY_BYPASS_ALLOWED_KEY         0x61707962 //bypa - bypass allowed
+#define KEY_CORNER_LEVEL_KEY           0x6E726F63 // corn - coner voltage
+#define KEY_ACTIVE_FLOOR               0x636676
+#define GENERIC_DISABLE                        0
+#define GENERIC_ENABLE                 1
+#define SW_MODE_LDO_IPEAK              1
+#define LDOA_RES_TYPE                  0x616F646C //aodl
+#define SMPS_RES_TYPE                  0x61706D73 //apms
+
+#ifdef CONFIG_QCOM_SMD_RPM
+typedef enum {
+       RPM_REQUEST_TYPE,
+       RPM_CMD_TYPE,
+       RPM_SUCCESS_REQ_ACK,
+       RPM_SUCCESS_CMD_ACK,
+       RPM_ERROR_ACK,
+} msg_type;
+
+enum {
+       RESOURCETYPE,
+       RESOURCEID,
+       KVP_KEY,
+       KVP_LENGTH,
+       KVP_VALUE,
+};
+
+typedef struct {
+       uint32_t type;
+       uint32_t len;
+} rpm_gen_hdr;
+
+typedef struct {
+       uint32_t key;
+       uint32_t len;
+       uint32_t val;
+} kvp_data;
+
+typedef struct {
+       uint32_t id;
+       uint32_t set;
+       uint32_t resourceType;
+       uint32_t resourceId;
+       uint32_t dataLength;
+} rpm_req_hdr;
+
+typedef struct {
+       rpm_gen_hdr hdr;
+       rpm_req_hdr req_hdr;
+       uint32_t payload[];
+} rpm_req;
+
+typedef struct {
+       rpm_gen_hdr hdr;
+       kvp_data *data;
+} rpm_cmd;
+
+typedef struct {
+       rpm_gen_hdr hdr;
+       uint32_t id;
+       uint32_t len;
+       uint32_t seq;
+} rpm_ack_msg;
+
+int rpm_send_data(uint32_t *data, uint32_t len, msg_type type);
+uint32_t rpm_recv_data(uint32_t *data, uint32_t *len);
+void rpm_clk_enable(uint32_t *data, uint32_t len);
+void rpm_clk_disable(uint32_t *data, uint32_t len);
+void smd_rpm_init(void);
+void smd_rpm_uninit(void);
+#else /* !CONFIG_QCOM_SMD_RPM */
+static inline void smd_rpm_init(void)
+{
+}
+
+static inline void smd_rpm_uninit(void)
+{
+}
+#endif /* !CONFIG_QCOM_SMD_RPM */
+
+#endif /* __PMIC_QCOM_SMD_RPM_H */