aboutsummaryrefslogtreecommitdiff
path: root/drivers/soc/qcom
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/soc/qcom')
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/cpu_ops.c343
-rw-r--r--drivers/soc/qcom/smd-rpm.c3
-rw-r--r--drivers/soc/qcom/smd.c146
-rw-r--r--drivers/soc/qcom/smem.c122
-rw-r--r--drivers/soc/qcom/spm.c172
6 files changed, 680 insertions, 107 deletions
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 10a93d168e0e..41b668b0d836 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -1,3 +1,4 @@
+obj-$(CONFIG_ARM64) += cpu_ops.o
obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o
obj-$(CONFIG_QCOM_PM) += spm.o
obj-$(CONFIG_QCOM_SMD) += smd.o
diff --git a/drivers/soc/qcom/cpu_ops.c b/drivers/soc/qcom/cpu_ops.c
new file mode 100644
index 000000000000..d831cb071d3d
--- /dev/null
+++ b/drivers/soc/qcom/cpu_ops.c
@@ -0,0 +1,343 @@
+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2013 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/* MSM ARMv8 CPU Operations
+ * Based on arch/arm64/kernel/smp_spin_table.c
+ */
+
+#include <linux/bitops.h>
+#include <linux/cpu.h>
+#include <linux/cpumask.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/smp.h>
+#include <linux/qcom_scm.h>
+
+#include <asm/barrier.h>
+#include <asm/cacheflush.h>
+#include <asm/cpu_ops.h>
+#include <asm/cputype.h>
+#include <asm/smp_plat.h>
+
+static DEFINE_RAW_SPINLOCK(boot_lock);
+
+DEFINE_PER_CPU(int, cold_boot_done);
+
+#if 0
+static int cold_boot_flags[] = {
+ 0,
+ QCOM_SCM_FLAG_COLDBOOT_CPU1,
+ QCOM_SCM_FLAG_COLDBOOT_CPU2,
+ QCOM_SCM_FLAG_COLDBOOT_CPU3,
+};
+#endif
+
+/* CPU power domain register offsets */
+#define CPU_PWR_CTL 0x4
+#define CPU_PWR_GATE_CTL 0x14
+#define LDO_BHS_PWR_CTL 0x28
+
+/* L2 power domain register offsets */
+#define L2_PWR_CTL_OVERRIDE 0xc
+#define L2_PWR_CTL 0x14
+#define L2_PWR_STATUS 0x18
+#define L2_CORE_CBCR 0x58
+#define L1_RST_DIS 0x284
+
+#define L2_SPM_STS 0xc
+#define L2_VREG_CTL 0x1c
+
+#define SCM_IO_READ 1
+#define SCM_IO_WRITE 2
+
+/*
+ * struct msm_l2ccc_of_info: represents of data for l2 cache clock controller.
+ * @compat: compat string for l2 cache clock controller
+ * @l2_pon: l2 cache power on routine
+ */
+struct msm_l2ccc_of_info {
+ const char *compat;
+ int (*l2_power_on) (struct device_node *dn, u32 l2_mask, int cpu);
+ u32 l2_power_on_mask;
+};
+
+
+static int power_on_l2_msm8916(struct device_node *l2ccc_node, u32 pon_mask,
+ int cpu)
+{
+ u32 pon_status;
+ void __iomem *l2_base;
+
+ l2_base = of_iomap(l2ccc_node, 0);
+ if (!l2_base)
+ return -ENOMEM;
+
+ /* Skip power-on sequence if l2 cache is already powered up*/
+ pon_status = (__raw_readl(l2_base + L2_PWR_STATUS) & pon_mask)
+ == pon_mask;
+ if (pon_status) {
+ iounmap(l2_base);
+ return 0;
+ }
+
+ /* Close L2/SCU Logic GDHS and power up the cache */
+ writel_relaxed(0x10D700, l2_base + L2_PWR_CTL);
+
+ /* Assert PRESETDBGn */
+ writel_relaxed(0x400000, l2_base + L2_PWR_CTL_OVERRIDE);
+ mb();
+ udelay(2);
+
+ /* De-assert L2/SCU memory Clamp */
+ writel_relaxed(0x101700, l2_base + L2_PWR_CTL);
+
+ /* Wakeup L2/SCU RAMs by deasserting sleep signals */
+ writel_relaxed(0x101703, l2_base + L2_PWR_CTL);
+ mb();
+ udelay(2);
+
+ /* Enable clocks via SW_CLK_EN */
+ writel_relaxed(0x01, l2_base + L2_CORE_CBCR);
+
+ /* De-assert L2/SCU logic clamp */
+ writel_relaxed(0x101603, l2_base + L2_PWR_CTL);
+ mb();
+ udelay(2);
+
+ /* De-assert PRESSETDBg */
+ writel_relaxed(0x0, l2_base + L2_PWR_CTL_OVERRIDE);
+
+ /* De-assert L2/SCU Logic reset */
+ writel_relaxed(0x100203, l2_base + L2_PWR_CTL);
+ mb();
+ udelay(54);
+
+ /* Turn on the PMIC_APC */
+ writel_relaxed(0x10100203, l2_base + L2_PWR_CTL);
+
+ /* Set H/W clock control for the cpu CBC block */
+ writel_relaxed(0x03, l2_base + L2_CORE_CBCR);
+ mb();
+ iounmap(l2_base);
+
+ return 0;
+}
+
+static const struct msm_l2ccc_of_info l2ccc_info[] = {
+ {
+ .compat = "qcom,8916-l2ccc",
+ .l2_power_on = power_on_l2_msm8916,
+ .l2_power_on_mask = BIT(9),
+ },
+};
+
+static int power_on_l2_cache(struct device_node *l2ccc_node, int cpu)
+{
+ int ret, i;
+ const char *compat;
+
+ ret = of_property_read_string(l2ccc_node, "compatible", &compat);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(l2ccc_info); i++) {
+ const struct msm_l2ccc_of_info *ptr = &l2ccc_info[i];
+
+ if (!of_compat_cmp(ptr->compat, compat, strlen(compat)))
+ return ptr->l2_power_on(l2ccc_node,
+ ptr->l2_power_on_mask, cpu);
+ }
+ pr_err("Compat string not found for L2CCC node\n");
+ return -EIO;
+}
+
+static int msm_unclamp_secondary_arm_cpu(unsigned int cpu)
+{
+
+ int ret = 0;
+ struct device_node *cpu_node, *acc_node, *l2_node, *l2ccc_node;
+ void __iomem *reg;
+
+ cpu_node = of_get_cpu_node(cpu, NULL);
+ if (!cpu_node)
+ return -ENODEV;
+
+ acc_node = of_parse_phandle(cpu_node, "qcom,acc", 0);
+ if (!acc_node) {
+ ret = -ENODEV;
+ goto out_acc;
+ }
+
+ l2_node = of_parse_phandle(cpu_node, "next-level-cache", 0);
+ if (!l2_node) {
+ ret = -ENODEV;
+ goto out_l2;
+ }
+
+ l2ccc_node = of_parse_phandle(l2_node, "power-domain", 0);
+ if (!l2ccc_node) {
+ ret = -ENODEV;
+ goto out_l2;
+ }
+
+ /* Ensure L2-cache of the CPU is powered on before
+ * unclamping cpu power rails.
+ */
+ ret = power_on_l2_cache(l2ccc_node, cpu);
+ if (ret) {
+ pr_err("L2 cache power up failed for CPU%d\n", cpu);
+ goto out_l2ccc;
+ }
+
+ reg = of_iomap(acc_node, 0);
+ if (!reg) {
+ ret = -ENOMEM;
+ goto out_acc_reg;
+ }
+
+ /* Assert Reset on cpu-n */
+ writel_relaxed(0x00000033, reg + CPU_PWR_CTL);
+ mb();
+
+ /*Program skew to 16 X0 clock cycles*/
+ writel_relaxed(0x10000001, reg + CPU_PWR_GATE_CTL);
+ mb();
+ udelay(2);
+
+ /* De-assert coremem clamp */
+ writel_relaxed(0x00000031, reg + CPU_PWR_CTL);
+ mb();
+
+ /* Close coremem array gdhs */
+ writel_relaxed(0x00000039, reg + CPU_PWR_CTL);
+ mb();
+ udelay(2);
+
+ /* De-assert cpu-n clamp */
+ writel_relaxed(0x00020038, reg + CPU_PWR_CTL);
+ mb();
+ udelay(2);
+
+ /* De-assert cpu-n reset */
+ writel_relaxed(0x00020008, reg + CPU_PWR_CTL);
+ mb();
+
+ /* Assert PWRDUP signal on core-n */
+ writel_relaxed(0x00020088, reg + CPU_PWR_CTL);
+ mb();
+
+ /* Secondary CPU-N is now alive */
+ iounmap(reg);
+out_acc_reg:
+ of_node_put(l2ccc_node);
+out_l2ccc:
+ of_node_put(l2_node);
+out_l2:
+ of_node_put(acc_node);
+out_acc:
+ of_node_put(cpu_node);
+
+ return ret;
+}
+
+static void write_pen_release(u64 val)
+{
+ void *start = (void *)&secondary_holding_pen_release;
+ unsigned long size = sizeof(secondary_holding_pen_release);
+
+ secondary_holding_pen_release = val;
+ smp_wmb();
+ __flush_dcache_area(start, size);
+}
+
+static int secondary_pen_release(unsigned int cpu)
+{
+ unsigned long timeout;
+
+ /*
+ * Set synchronisation state between this boot processor
+ * and the secondary one
+ */
+ raw_spin_lock(&boot_lock);
+ write_pen_release(cpu_logical_map(cpu));
+
+ timeout = jiffies + (1 * HZ);
+ while (time_before(jiffies, timeout)) {
+ if (secondary_holding_pen_release == INVALID_HWID)
+ break;
+ udelay(10);
+ }
+ raw_spin_unlock(&boot_lock);
+
+ return secondary_holding_pen_release != INVALID_HWID ? -ENOSYS : 0;
+}
+
+static int __init msm_cpu_init(struct device_node *dn, unsigned int cpu)
+{
+ /* Mark CPU0 cold boot flag as done */
+ if (!cpu && !per_cpu(cold_boot_done, cpu))
+ per_cpu(cold_boot_done, cpu) = true;
+
+ return 0;
+}
+
+static int __init msm_cpu_prepare(unsigned int cpu)
+{
+ const cpumask_t *mask = cpumask_of(cpu);
+
+ if (qcom_scm_set_cold_boot_addr(secondary_holding_pen, mask)) {
+ pr_warn("CPU%d:Failed to set boot address\n", cpu);
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+static int msm_cpu_boot(unsigned int cpu)
+{
+ int ret = 0;
+
+ if (per_cpu(cold_boot_done, cpu) == false) {
+ ret = msm_unclamp_secondary_arm_cpu(cpu);
+ if (ret)
+ return ret;
+ per_cpu(cold_boot_done, cpu) = true;
+ }
+ return secondary_pen_release(cpu);
+}
+
+void msm_cpu_postboot(void)
+{
+ /*
+ * Let the primary processor know we're out of the pen.
+ */
+ write_pen_release(INVALID_HWID);
+
+ /*
+ * Synchronise with the boot thread.
+ */
+ raw_spin_lock(&boot_lock);
+ raw_spin_unlock(&boot_lock);
+}
+
+static const struct cpu_operations msm_cortex_a_ops = {
+ .name = "qcom,arm-cortex-acc",
+ .cpu_init = msm_cpu_init,
+ .cpu_prepare = msm_cpu_prepare,
+ .cpu_boot = msm_cpu_boot,
+ .cpu_postboot = msm_cpu_postboot,
+};
+CPU_METHOD_OF_DECLARE(msm_cortex_a_ops, &msm_cortex_a_ops);
diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c
index 1392ccf14a20..f58d02e51bb8 100644
--- a/drivers/soc/qcom/smd-rpm.c
+++ b/drivers/soc/qcom/smd-rpm.c
@@ -121,7 +121,7 @@ int qcom_rpm_smd_write(struct qcom_smd_rpm *rpm,
pkt.hdr.length = sizeof(struct qcom_rpm_request) + count;
pkt.req.msg_id = msg_id++;
- pkt.req.flags = BIT(state);
+ pkt.req.flags = state;
pkt.req.type = type;
pkt.req.id = id;
pkt.req.data_len = count;
@@ -212,6 +212,7 @@ static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev)
static const struct of_device_id qcom_smd_rpm_of_match[] = {
{ .compatible = "qcom,rpm-msm8974" },
+ { .compatible = "qcom,rpm-msm8916" },
{}
};
MODULE_DEVICE_TABLE(of, qcom_smd_rpm_of_match);
diff --git a/drivers/soc/qcom/smd.c b/drivers/soc/qcom/smd.c
index a6155c917d52..576c89fd8965 100644
--- a/drivers/soc/qcom/smd.c
+++ b/drivers/soc/qcom/smd.c
@@ -131,18 +131,6 @@ struct qcom_smd_edge {
struct work_struct work;
};
-/*
- * SMD channel states.
- */
-enum smd_channel_state {
- SMD_CHANNEL_CLOSED,
- SMD_CHANNEL_OPENING,
- SMD_CHANNEL_OPENED,
- SMD_CHANNEL_FLUSHING,
- SMD_CHANNEL_CLOSING,
- SMD_CHANNEL_RESET,
- SMD_CHANNEL_RESET_OPENING
-};
/**
* struct qcom_smd_channel - smd channel struct
@@ -166,38 +154,6 @@ enum smd_channel_state {
* @pkt_size: size of the currently handled packet
* @list: lite entry for @channels in qcom_smd_edge
*/
-struct qcom_smd_channel {
- struct qcom_smd_edge *edge;
-
- struct qcom_smd_device *qsdev;
-
- char *name;
- enum smd_channel_state state;
- enum smd_channel_state remote_state;
-
- struct smd_channel_info *tx_info;
- struct smd_channel_info *rx_info;
-
- struct smd_channel_info_word *tx_info_word;
- struct smd_channel_info_word *rx_info_word;
-
- struct mutex tx_lock;
- wait_queue_head_t fblockread_event;
-
- void *tx_fifo;
- void *rx_fifo;
- int fifo_size;
-
- void *bounce_buffer;
- int (*cb)(struct qcom_smd_device *, const void *, size_t);
-
- spinlock_t recv_lock;
-
- int pkt_size;
-
- struct list_head list;
-};
-
/**
* struct qcom_smd - smd struct
* @dev: device struct
@@ -362,43 +318,37 @@ static void qcom_smd_channel_set_state(struct qcom_smd_channel *channel,
}
/*
- * Copy count bytes of data using 32bit accesses, if that's required.
+ * Copy count bytes of data from memory to device memory using 32bit accesses
*/
-static void smd_copy_to_fifo(void __iomem *_dst,
- const void *_src,
- size_t count,
- bool word_aligned)
+static void smd_copy_to_fifo(void __iomem *_dst, const void *_src, size_t count, bool word_aligned)
{
- u32 *dst = (u32 *)_dst;
- u32 *src = (u32 *)_src;
-
- if (word_aligned) {
- count /= sizeof(u32);
- while (count--)
- writel_relaxed(*src++, dst++);
- } else {
- memcpy_toio(_dst, _src, count);
- }
+ u32 *dst = (u32 *)_dst;
+ u32 *src = (u32 *)_src;
+
+ if (word_aligned) {
+ count /= sizeof(u32);
+ while (count--)
+ writel_relaxed(*src++, dst++);
+ } else {
+ memcpy_toio(_dst, _src, count);
+ }
}
/*
- * Copy count bytes of data using 32bit accesses, if that is required.
+ * Copy count bytes of data from device memory to memory using 32bit accesses
*/
-static void smd_copy_from_fifo(void *_dst,
- const void __iomem *_src,
- size_t count,
- bool word_aligned)
+static void smd_copy_from_fifo(void *_dst, const void __iomem *_src, size_t count, bool word_aligned)
{
- u32 *dst = (u32 *)_dst;
- u32 *src = (u32 *)_src;
-
- if (word_aligned) {
- count /= sizeof(u32);
- while (count--)
- *dst++ = readl_relaxed(src++);
- } else {
- memcpy_fromio(_dst, _src, count);
- }
+ u32 *dst = (u32 *)_dst;
+ u32 *src = (u32 *)_src;
+
+ if (word_aligned) {
+ count /= sizeof(u32);
+ while (count--)
+ *dst++ = readl_relaxed(src++);
+ } else {
+ memcpy_fromio(_dst, _src, count);
+ }
}
/*
@@ -416,19 +366,11 @@ static size_t qcom_smd_channel_peek(struct qcom_smd_channel *channel,
tail = GET_RX_CHANNEL_INFO(channel, tail);
len = min_t(size_t, count, channel->fifo_size - tail);
- if (len) {
- smd_copy_from_fifo(buf,
- channel->rx_fifo + tail,
- len,
- word_aligned);
- }
+ if (len)
+ smd_copy_from_fifo(buf, channel->rx_fifo + tail, len, word_aligned);
- if (len != count) {
- smd_copy_from_fifo(buf + len,
- channel->rx_fifo,
- count - len,
- word_aligned);
- }
+ if (len != count)
+ smd_copy_from_fifo(buf + len, channel->rx_fifo, count - len, word_aligned);
return count;
}
@@ -631,19 +573,11 @@ static int qcom_smd_write_fifo(struct qcom_smd_channel *channel,
head = GET_TX_CHANNEL_INFO(channel, head);
len = min_t(size_t, count, channel->fifo_size - head);
- if (len) {
- smd_copy_to_fifo(channel->tx_fifo + head,
- data,
- len,
- word_aligned);
- }
+ if (len)
+ smd_copy_to_fifo(channel->tx_fifo + head, data, len, word_aligned);
- if (len != count) {
- smd_copy_to_fifo(channel->tx_fifo,
- data + len,
- count - len,
- word_aligned);
- }
+ if (len != count)
+ smd_copy_to_fifo(channel->tx_fifo, data + len, count - len, word_aligned);
head += count;
head &= (channel->fifo_size - 1);
@@ -667,16 +601,19 @@ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len)
{
u32 hdr[5] = {len,};
int tlen = sizeof(hdr) + len;
- int ret;
+ int ret, length;
/* Word aligned channels only accept word size aligned data */
if (channel->rx_info_word != NULL && len % 4)
return -EINVAL;
+ length = qcom_smd_get_tx_avail(channel);
+
ret = mutex_lock_interruptible(&channel->tx_lock);
if (ret)
return ret;
+ length = qcom_smd_get_tx_avail(channel);
while (qcom_smd_get_tx_avail(channel) < tlen) {
if (channel->state != SMD_CHANNEL_OPENED) {
ret = -EPIPE;
@@ -696,9 +633,12 @@ int qcom_smd_send(struct qcom_smd_channel *channel, const void *data, int len)
SET_TX_CHANNEL_INFO(channel, fTAIL, 0);
+ length = qcom_smd_get_tx_avail(channel);
qcom_smd_write_fifo(channel, hdr, sizeof(hdr));
qcom_smd_write_fifo(channel, data, len);
+ length = qcom_smd_get_tx_avail(channel);
+
SET_TX_CHANNEL_INFO(channel, fHEAD, 1);
/* Ensure ordering of channel info updates */
@@ -1008,7 +948,7 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed
/* The channel consist of a rx and tx fifo of equal size */
fifo_size /= 2;
- dev_dbg(smd->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n",
+ dev_err(smd->dev, "new channel '%s' info-size: %zu fifo-size: %zu\n",
name, info_size, fifo_size);
channel->tx_fifo = fifo_base;
@@ -1190,7 +1130,11 @@ static int qcom_smd_parse_edge(struct device *dev,
edge->remote_pid = QCOM_SMEM_HOST_ANY;
key = "qcom,remote-pid";
- of_property_read_u32(node, key, &edge->remote_pid);
+ ret = of_property_read_u32(node, key, &edge->remote_pid);
+ if (ret) {
+ dev_err(dev, "edge missing %s property\n", key);
+ return -EINVAL;
+ }
syscon_np = of_parse_phandle(node, "qcom,ipc", 0);
if (!syscon_np) {
diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
index 52365188a1c2..7fddf3b491b6 100644
--- a/drivers/soc/qcom/smem.c
+++ b/drivers/soc/qcom/smem.c
@@ -20,6 +20,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/soc/qcom/smem.h>
+#include <linux/debugfs.h>
/*
* The Qualcomm shared memory system is a allocate only heap structure that
@@ -85,6 +86,8 @@
/* Max number of processors/hosts in a system */
#define SMEM_HOST_COUNT 9
+#define SMEM_HEAP_INFO 1
+
/**
* struct smem_proc_comm - proc_comm communication struct (legacy)
* @command: current command to be executed
@@ -238,6 +241,9 @@ struct qcom_smem {
struct smem_partition_header *partitions[SMEM_HOST_COUNT];
+ struct dentry *dent;
+ u32 version;
+
unsigned num_regions;
struct smem_region regions[0];
};
@@ -642,6 +648,104 @@ static int qcom_smem_count_mem_regions(struct platform_device *pdev)
return num_regions;
}
+static void smem_debug_read_mem(struct seq_file *s)
+{
+ u32 *info;
+ size_t size;
+ int ret, i;
+ long flags;
+
+ ret = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_HEAP_INFO,
+ (void **)&info, &size);
+
+ if (ret < 0)
+ seq_printf(s, "Can't get global heap information pool\n");
+ else {
+ seq_printf(s, "global heap\n");
+ seq_printf(s, " initialized: %d offset: %08x avail: %08x\n",
+ info[0], info[1], info[2]);
+
+ for (i = 0; i < 512; i++) {
+ ret = qcom_smem_get(QCOM_SMEM_HOST_ANY, i,
+ (void **)&info, &size);
+ if (ret < 0)
+ continue;
+
+ seq_printf(s, " [%d]: p: %p s: %li\n", i, info,
+ size);
+ }
+ }
+
+ seq_printf(s, "\nSecure partitions accessible from APPS:\n");
+
+ ret = hwspin_lock_timeout_irqsave(__smem->hwlock,
+ HWSPINLOCK_TIMEOUT,
+ &flags);
+
+ for (i = 0; i < SMEM_HOST_COUNT; i++) {
+ struct smem_partition_header *part_hdr = __smem->partitions[i];
+ void *p;
+
+ if (!part_hdr)
+ continue;
+
+ if (part_hdr->magic != SMEM_PART_MAGIC) {
+ seq_printf(s, " part[%d]: incorrect magic\n", i);
+ continue;
+ }
+
+ seq_printf(s, " part[%d]: (%d <-> %d) size: %d off: %08x\n",
+ i, part_hdr->host0, part_hdr->host1, part_hdr->size,
+ part_hdr->offset_free_uncached);
+
+ p = (void *)part_hdr + sizeof(*part_hdr);
+ while (p < (void *)part_hdr + part_hdr->offset_free_uncached) {
+ struct smem_private_entry *entry = p;
+
+ seq_printf(s,
+ " [%d]: %s size: %d pd: %d\n",
+ entry->item,
+ (entry->canary == SMEM_PRIVATE_CANARY) ?
+ "valid" : "invalid",
+ entry->size,
+ entry->padding_data);
+
+ p += sizeof(*entry) + entry->padding_hdr + entry->size;
+ }
+ }
+
+ hwspin_unlock_irqrestore(__smem->hwlock, &flags);
+}
+
+static void smem_debug_read_version(struct seq_file *s)
+{
+ seq_printf(s, "SBL version: %08x\n", __smem->version >> 16);
+}
+
+static int debugfs_show(struct seq_file *s, void *data)
+{
+ void (*show)(struct seq_file *) = s->private;
+
+ show(s);
+
+ return 0;
+}
+
+static int smem_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, debugfs_show, inode->i_private);
+}
+
+
+static const struct file_operations smem_debug_ops = {
+ .open = smem_debug_open,
+ .release = single_release,
+ .read = seq_read,
+ .llseek = seq_lseek,
+};
+
+
+
static int qcom_smem_probe(struct platform_device *pdev)
{
struct smem_header *header;
@@ -652,7 +756,6 @@ static int qcom_smem_probe(struct platform_device *pdev)
size_t array_size;
int num_regions = 0;
int hwlock_id;
- u32 version;
int ret;
int i;
@@ -703,9 +806,9 @@ static int qcom_smem_probe(struct platform_device *pdev)
return -EINVAL;
}
- version = qcom_smem_get_sbl_version(smem);
- if (version >> 16 != SMEM_EXPECTED_VERSION) {
- dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", version);
+ smem->version = qcom_smem_get_sbl_version(smem);
+ if (smem->version >> 16 != SMEM_EXPECTED_VERSION) {
+ dev_err(&pdev->dev, "Unsupported SMEM version 0x%x\n", smem->version);
return -EINVAL;
}
@@ -725,6 +828,17 @@ static int qcom_smem_probe(struct platform_device *pdev)
__smem = smem;
+ /* setup debugfs information */
+ __smem->dent = debugfs_create_dir("smem", 0);
+ if (IS_ERR(__smem->dent))
+ dev_info(smem->dev, "unable to create debugfs\n");
+
+ if (!debugfs_create_file("mem", 0444, __smem->dent, smem_debug_read_mem, &smem_debug_ops))
+ dev_err(smem->dev, "couldnt create mem file\n");
+ if (!debugfs_create_file("version", 0444, __smem->dent, smem_debug_read_version,
+ &smem_debug_ops))
+ dev_err(smem->dev, "couldnt create mem file\n");
+
return 0;
}
diff --git a/drivers/soc/qcom/spm.c b/drivers/soc/qcom/spm.c
index b04b05a0904e..7f751c5a4c94 100644
--- a/drivers/soc/qcom/spm.c
+++ b/drivers/soc/qcom/spm.c
@@ -20,11 +20,14 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
+#include <linux/delay.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/cpuidle.h>
#include <linux/cpu_pm.h>
#include <linux/qcom_scm.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
#include <asm/cpuidle.h>
#include <asm/proc-fns.h>
@@ -36,6 +39,9 @@
#define SPM_CTL_INDEX_SHIFT 4
#define SPM_CTL_EN BIT(0)
+/* Specifies the PMIC internal slew rate in uV/us. */
+#define REGULATOR_SLEW_RATE 1250
+
enum pm_sleep_mode {
PM_SLEEP_MODE_STBY,
PM_SLEEP_MODE_RET,
@@ -51,6 +57,8 @@ enum spm_reg {
SPM_REG_PMIC_DLY,
SPM_REG_PMIC_DATA_0,
SPM_REG_PMIC_DATA_1,
+ SPM_REG_RST,
+ SPM_REG_STS_1,
SPM_REG_VCTL,
SPM_REG_SEQ_ENTRY,
SPM_REG_SPM_STS,
@@ -68,9 +76,23 @@ struct spm_reg_data {
u8 start_index[PM_SLEEP_MODE_NR];
};
+struct spm_vlevel_data {
+ struct spm_driver_data *drv;
+ unsigned selector;
+};
+
+struct saw2_vreg {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+ unsigned int uV;
+ u32 vlevel;
+ struct spm_driver_data *drv;
+};
+
struct spm_driver_data {
void __iomem *reg_base;
const struct spm_reg_data *reg_data;
+ struct saw2_vreg *vreg;
};
static const u8 spm_reg_offset_v2_1[SPM_REG_NR] = {
@@ -94,10 +116,13 @@ static const struct spm_reg_data spm_reg_8974_8084_cpu = {
static const u8 spm_reg_offset_v1_1[SPM_REG_NR] = {
[SPM_REG_CFG] = 0x08,
+ [SPM_REG_STS_1] = 0x10,
+ [SPM_REG_VCTL] = 0x14,
[SPM_REG_SPM_CTL] = 0x20,
[SPM_REG_PMIC_DLY] = 0x24,
[SPM_REG_PMIC_DATA_0] = 0x28,
[SPM_REG_PMIC_DATA_1] = 0x2C,
+ [SPM_REG_RST] = 0x30,
[SPM_REG_SEQ_ENTRY] = 0x80,
};
@@ -282,6 +307,146 @@ static struct cpuidle_ops qcom_cpuidle_ops __initdata = {
CPUIDLE_METHOD_OF_DECLARE(qcom_idle_v1, "qcom,kpss-acc-v1", &qcom_cpuidle_ops);
CPUIDLE_METHOD_OF_DECLARE(qcom_idle_v2, "qcom,kpss-acc-v2", &qcom_cpuidle_ops);
+static const unsigned int saw2_volt_table[] = {
+ 850000, 862500, 875000, 887500, 900000, 912500,
+ 925000, 937500, 950000, 962500, 975000, 987500,
+ 1000000, 1012500, 1025000, 1037500, 1050000, 1062500,
+ 1075000, 1087500, 1100000, 1112500, 1125000, 1137000,
+ 1137500, 1150000, 1162500, 1175000, 1187500, 1200000,
+ 1212500, 1225000, 1237500, 1250000, 1287500
+};
+
+static const u32 vlevels[] = {
+ 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+ 0x94, 0x96, 0x96, 0x96, 0x98, 0x98,
+ 0x98, 0x9a, 0x9a, 0x9e, 0xa0, 0xa0,
+ 0xa2, 0xa6, 0xa8, 0xa8, 0xaa, 0xaa,
+ 0xac, 0xac, 0xac, 0xac, 0xac, 0xac,
+ 0xac, 0xac, 0xac, 0xac, 0xac
+};
+
+static int saw2_regulator_get_voltage(struct regulator_dev *rdev)
+{
+ struct spm_driver_data *drv = rdev_get_drvdata(rdev);
+
+ return drv->vreg->uV;
+}
+
+static void spm_smp_set_vdd(void *data)
+{
+ struct spm_vlevel_data *vdata = (struct spm_vlevel_data *)data;
+ struct spm_driver_data *drv = vdata->drv;
+ struct saw2_vreg *vreg = drv->vreg;
+ unsigned long sel = vdata->selector;
+ u32 new_vlevel;
+ u32 vctl, data0, data1;
+ int timeout_us = 50;
+
+ if (vreg->vlevel == vlevels[sel])
+ return;
+
+ vctl = spm_register_read(drv, SPM_REG_VCTL);
+ data0 = spm_register_read(drv, SPM_REG_PMIC_DATA_0);
+ data1 = spm_register_read(drv, SPM_REG_PMIC_DATA_1);
+
+ vctl &= ~0xff;
+ vctl |= vlevels[sel];
+
+ data0 &= ~0xff;
+ data0 |= vlevels[sel];
+
+ data1 &= ~0x3f;
+ data1 |= (vlevels[sel] & 0x3f);
+ data1 &= ~0x3F0000;
+ data1 |= ((vlevels[sel] & 0x3f) << 16);
+
+ spm_register_write(drv, SPM_REG_RST, 1);
+ spm_register_write(drv, SPM_REG_VCTL, vctl);
+ spm_register_write(drv, SPM_REG_PMIC_DATA_0, data0);
+ spm_register_write(drv, SPM_REG_PMIC_DATA_1, data1);
+
+ do {
+ new_vlevel = spm_register_read(drv, SPM_REG_STS_1) & 0xff;
+ if (new_vlevel == vlevels[sel])
+ break;
+ udelay(1);
+ } while (--timeout_us);
+
+ if (!timeout_us) {
+ pr_info("%s: Voltage not changed %#x\n", __func__, new_vlevel);
+ return;
+ }
+
+ if (saw2_volt_table[sel] > vreg->uV) {
+ /* Wait for voltage to stabalize. */
+ udelay((saw2_volt_table[sel] - vreg->uV) / REGULATOR_SLEW_RATE);
+ }
+
+ vreg->uV = saw2_volt_table[sel];
+ vreg->vlevel = vlevels[sel];
+}
+
+static int saw2_regulator_set_voltage_sel(struct regulator_dev *rdev,
+ unsigned selector)
+{
+ struct spm_driver_data *drv = rdev_get_drvdata(rdev);
+ struct spm_vlevel_data data;
+ int ret;
+ int cpu = rdev_get_id(rdev);
+
+ data.drv = drv;
+ data.selector = selector;
+
+ ret = smp_call_function_single(cpu, spm_smp_set_vdd, &data, true);
+
+ return ret;
+}
+
+static struct regulator_ops saw2_regulator_ops = {
+ .list_voltage = regulator_list_voltage_table,
+ .map_voltage = regulator_map_voltage_iterate,
+ .get_voltage = saw2_regulator_get_voltage,
+ .set_voltage_sel = saw2_regulator_set_voltage_sel,
+};
+
+static struct regulator_desc saw2_regulator = {
+ .owner = THIS_MODULE,
+ .type = REGULATOR_VOLTAGE,
+ .ops = &saw2_regulator_ops,
+ .volt_table = saw2_volt_table,
+ .n_voltages = ARRAY_SIZE(saw2_volt_table),
+};
+
+static int register_saw2_regulator(struct spm_driver_data *drv,
+ struct platform_device *pdev, int cpu)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct saw2_vreg *vreg;
+ struct regulator_config config = { };
+
+ vreg = devm_kzalloc(&pdev->dev, sizeof(*vreg), GFP_KERNEL);
+ if (!vreg)
+ return -ENOMEM;
+
+ drv->vreg = vreg;
+ config.driver_data = drv;
+ config.dev = &pdev->dev;
+ config.of_node = np;
+
+ vreg->rdesc = saw2_regulator;
+ vreg->rdesc.id = cpu;
+ vreg->rdesc.name = of_get_property(np, "regulator-name", NULL);
+ config.init_data = of_get_regulator_init_data(&pdev->dev,
+ pdev->dev.of_node,
+ &vreg->rdesc);
+
+ vreg->rdev = devm_regulator_register(&pdev->dev, &vreg->rdesc, &config);
+ if (IS_ERR(vreg->rdev))
+ return PTR_ERR(vreg->rdev);
+
+ return 0;
+}
+
static struct spm_driver_data *spm_get_drv(struct platform_device *pdev,
int *spm_cpu)
{
@@ -327,7 +492,7 @@ static int spm_dev_probe(struct platform_device *pdev)
struct resource *res;
const struct of_device_id *match_id;
void __iomem *addr;
- int cpu;
+ int cpu, ret;
drv = spm_get_drv(pdev, &cpu);
if (!drv)
@@ -356,6 +521,7 @@ static int spm_dev_probe(struct platform_device *pdev)
* machine, before the sequences are completely written.
*/
spm_register_write(drv, SPM_REG_CFG, drv->reg_data->spm_cfg);
+
spm_register_write(drv, SPM_REG_DLY, drv->reg_data->spm_dly);
spm_register_write(drv, SPM_REG_PMIC_DLY, drv->reg_data->pmic_dly);
spm_register_write(drv, SPM_REG_PMIC_DATA_0,
@@ -368,6 +534,10 @@ static int spm_dev_probe(struct platform_device *pdev)
per_cpu(cpu_spm_drv, cpu) = drv;
+ ret = register_saw2_regulator(drv, pdev, cpu);
+ if (ret)
+ dev_err(&pdev->dev, "error registering SAW2 regulator\n");
+
return 0;
}