From: Lothar Waßmann Date: Mon, 13 Mar 2017 15:01:24 +0000 (+0100) Subject: misc: add support for Qualcomm SMD protocol X-Git-Tag: KARO-TXSD-2017-03-15~14 X-Git-Url: https://git.karo-electronics.de/?a=commitdiff_plain;h=fd58157dbbeca12fc2a81240b580329a6fb7effb;p=karo-tx-uboot.git misc: add support for Qualcomm SMD protocol --- 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 index 0000000000..f9c30c484c --- /dev/null +++ b/arch/arm/mach-snapdragon/include/mach/qcom-smd.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2017, Lothar Waßmann + * + * 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 */ diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index c6a753d576..5f8d705dbf 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -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" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 6287374a54..3c927378c9 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -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 index 0000000000..846505c636 --- /dev/null +++ b/drivers/misc/qcom-smd.c @@ -0,0 +1,673 @@ +/* + * Qualcomm Shared Memory driver + * + * Copyright (C) 2017, Lothar Waßmann + * + * 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 +#include +#include +#include +#include +#include +#include + +#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(); +} diff --git a/drivers/power/pmic/Makefile b/drivers/power/pmic/Makefile index 52b4f711fb..7ead298831 100644 --- a/drivers/power/pmic/Makefile +++ b/drivers/power/pmic/Makefile @@ -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 index 0000000000..7509b353e6 --- /dev/null +++ b/drivers/power/pmic/pmic-qcom-smd-rpm.c @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2017, Lothar Waßmann + * + * 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 +#include +#include +#include + +#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 index 0000000000..8eb9aa823a --- /dev/null +++ b/include/power/pmic-qcom-smd-rpm.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2017, Lothar Waßmann + * + * 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 */