diff options
Diffstat (limited to 'drivers/soc/qcom')
-rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
-rw-r--r-- | drivers/soc/qcom/cpu_ops.c | 343 | ||||
-rw-r--r-- | drivers/soc/qcom/smd-rpm.c | 3 | ||||
-rw-r--r-- | drivers/soc/qcom/smd.c | 396 | ||||
-rw-r--r-- | drivers/soc/qcom/smem.c | 122 | ||||
-rw-r--r-- | drivers/soc/qcom/spm.c | 172 |
6 files changed, 865 insertions, 172 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..b51332e0c54f 100644 --- a/drivers/soc/qcom/smd.c +++ b/drivers/soc/qcom/smd.c @@ -104,9 +104,9 @@ static const struct { * @channels: list of all channels detected on this edge * @channels_lock: guard for modifications of @channels * @allocated: array of bitmaps representing already allocated channels - * @need_rescan: flag that the @work needs to scan smem for new channels * @smem_available: last available amount of smem triggering a channel scan - * @work: work item for edge house keeping + * @scan_work: work item for discovering new channels + * @state_work: work item for edge state changes */ struct qcom_smd_edge { struct qcom_smd *smd; @@ -121,29 +121,19 @@ struct qcom_smd_edge { int ipc_bit; struct list_head channels; - spinlock_t channels_lock; + rwlock_t channels_lock; DECLARE_BITMAP(allocated[SMD_ALLOC_TBL_COUNT], SMD_ALLOC_TBL_SIZE); - bool need_rescan; unsigned smem_available; - struct work_struct work; -}; + wait_queue_head_t new_channel_event; -/* - * 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 work_struct scan_work; + struct work_struct state_work; }; + /** * struct qcom_smd_channel - smd channel struct * @edge: qcom_smd_edge this channel is living on @@ -166,38 +156,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 @@ -323,6 +281,19 @@ static void qcom_smd_channel_reset(struct qcom_smd_channel *channel) } /* + * Set the callback for a channel, with appropriate locking + */ +static void qcom_smd_channel_set_callback(struct qcom_smd_channel *channel, + qcom_smd_cb_t cb) +{ + unsigned long flags; + + spin_lock_irqsave(&channel->recv_lock, flags); + channel->cb = cb; + spin_unlock_irqrestore(&channel->recv_lock, flags); +}; + +/* * Calculate the amount of data available in the rx fifo */ static size_t qcom_smd_channel_get_rx_avail(struct qcom_smd_channel *channel) @@ -362,43 +333,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 +381,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; } @@ -561,13 +518,13 @@ static irqreturn_t qcom_smd_edge_intr(int irq, void *data) /* * Handle state changes or data on each of the channels on this edge */ - spin_lock(&edge->channels_lock); + read_lock(&edge->channels_lock); list_for_each_entry(channel, &edge->channels, list) { spin_lock(&channel->recv_lock); kick_worker |= qcom_smd_channel_intr(channel); spin_unlock(&channel->recv_lock); } - spin_unlock(&edge->channels_lock); + read_unlock(&edge->channels_lock); /* * Creating a new channel requires allocating an smem entry, so we only @@ -577,12 +534,11 @@ static irqreturn_t qcom_smd_edge_intr(int irq, void *data) available = qcom_smem_get_free_space(edge->remote_pid); if (available != edge->smem_available) { edge->smem_available = available; - edge->need_rescan = true; kick_worker = true; } if (kick_worker) - schedule_work(&edge->work); + schedule_work(&edge->scan_work); return IRQ_HANDLED; } @@ -631,19 +587,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 +615,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 +647,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 */ @@ -727,22 +681,29 @@ static struct qcom_smd_driver *to_smd_driver(struct device *dev) static int qcom_smd_dev_match(struct device *dev, struct device_driver *drv) { + struct qcom_smd_device *qsdev = to_smd_device(dev); + struct qcom_smd_driver *qsdrv = container_of(drv, struct qcom_smd_driver, driver); + const struct qcom_smd_id *match = qsdrv->smd_match_table; + const char *name = qsdev->channel->name; + + if (match) { + while (match->name[0]) { + if (!strcmp(match->name, name)) + return 1; + match++; + } + } + return of_driver_match_device(dev, drv); } /* - * Probe the smd client. - * - * The remote side have indicated that it want the channel to be opened, so - * complete the state handshake and probe our client driver. + * Helper for opening a channel */ -static int qcom_smd_dev_probe(struct device *dev) +static int qcom_smd_channel_open(struct qcom_smd_channel *channel, + qcom_smd_cb_t cb) { - struct qcom_smd_device *qsdev = to_smd_device(dev); - struct qcom_smd_driver *qsdrv = to_smd_driver(dev); - struct qcom_smd_channel *channel = qsdev->channel; size_t bb_size; - int ret; /* * Packets are maximum 4k, but reduce if the fifo is smaller @@ -752,12 +713,44 @@ static int qcom_smd_dev_probe(struct device *dev) if (!channel->bounce_buffer) return -ENOMEM; - channel->cb = qsdrv->callback; - + qcom_smd_channel_set_callback(channel, cb); qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENING); - qcom_smd_channel_set_state(channel, SMD_CHANNEL_OPENED); + return 0; +} + +/* + * Helper for closing and resetting a channel + */ +static void qcom_smd_channel_close(struct qcom_smd_channel *channel) +{ + qcom_smd_channel_set_callback(channel, NULL); + + kfree(channel->bounce_buffer); + channel->bounce_buffer = NULL; + + qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); + qcom_smd_channel_reset(channel); +} + +/* + * Probe the smd client. + * + * The remote side have indicated that it want the channel to be opened, so + * complete the state handshake and probe our client driver. + */ +static int qcom_smd_dev_probe(struct device *dev) +{ + struct qcom_smd_device *qsdev = to_smd_device(dev); + struct qcom_smd_driver *qsdrv = to_smd_driver(dev); + struct qcom_smd_channel *channel = qsdev->channel; + int ret; + + ret = qcom_smd_channel_open(channel, qsdrv->callback); + if (ret) + return ret; + ret = qsdrv->probe(qsdev); if (ret) goto err; @@ -769,11 +762,7 @@ static int qcom_smd_dev_probe(struct device *dev) err: dev_err(&qsdev->dev, "probe failed\n"); - channel->cb = NULL; - kfree(channel->bounce_buffer); - channel->bounce_buffer = NULL; - - qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); + qcom_smd_channel_close(channel); return ret; } @@ -788,16 +777,15 @@ static int qcom_smd_dev_remove(struct device *dev) struct qcom_smd_device *qsdev = to_smd_device(dev); struct qcom_smd_driver *qsdrv = to_smd_driver(dev); struct qcom_smd_channel *channel = qsdev->channel; - unsigned long flags; + struct qcom_smd_channel *tmp; + struct qcom_smd_channel *ch; qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSING); /* * Make sure we don't race with the code receiving data. */ - spin_lock_irqsave(&channel->recv_lock, flags); - channel->cb = NULL; - spin_unlock_irqrestore(&channel->recv_lock, flags); + qcom_smd_channel_set_callback(channel, NULL); /* Wake up any sleepers in qcom_smd_send() */ wake_up_interruptible(&channel->fblockread_event); @@ -810,15 +798,14 @@ static int qcom_smd_dev_remove(struct device *dev) qsdrv->remove(qsdev); /* - * The client is now gone, cleanup and reset the channel state. + * The client is now gone, close and release all channels associated + * with this sdev */ - channel->qsdev = NULL; - kfree(channel->bounce_buffer); - channel->bounce_buffer = NULL; - - qcom_smd_channel_set_state(channel, SMD_CHANNEL_CLOSED); - - qcom_smd_channel_reset(channel); + list_for_each_entry_safe(ch, tmp, &channel->dev_list, dev_list) { + qcom_smd_channel_close(ch); + list_del(&ch->dev_list); + ch->qsdev = NULL; + } return 0; } @@ -880,19 +867,17 @@ static int qcom_smd_create_device(struct qcom_smd_channel *channel) if (channel->qsdev) return -EEXIST; - node = qcom_smd_match_channel(edge->of_node, channel->name); - if (!node) { - dev_dbg(smd->dev, "no match for '%s'\n", channel->name); - return -ENXIO; - } - dev_dbg(smd->dev, "registering '%s'\n", channel->name); qsdev = kzalloc(sizeof(*qsdev), GFP_KERNEL); if (!qsdev) return -ENOMEM; - dev_set_name(&qsdev->dev, "%s.%s", edge->of_node->name, node->name); + node = qcom_smd_match_channel(edge->of_node, channel->name); + dev_set_name(&qsdev->dev, "%s.%s", + edge->of_node->name, + node ? node->name : channel->name); + qsdev->dev.parent = smd->dev; qsdev->dev.bus = &qcom_smd_bus; qsdev->dev.release = qcom_smd_release_device; @@ -948,6 +933,76 @@ void qcom_smd_driver_unregister(struct qcom_smd_driver *qsdrv) } EXPORT_SYMBOL(qcom_smd_driver_unregister); +static struct qcom_smd_channel * +qcom_smd_find_channel(struct qcom_smd_edge *edge, const char *name) +{ + struct qcom_smd_channel *channel; + struct qcom_smd_channel *ret = NULL; + unsigned state; + + read_lock(&edge->channels_lock); + list_for_each_entry(channel, &edge->channels, list) { + if (strcmp(channel->name, name)) + continue; + + state = GET_RX_CHANNEL_INFO(channel, state); + if (state != SMD_CHANNEL_OPENING && + state != SMD_CHANNEL_OPENED) + continue; + + ret = channel; + break; + } + read_unlock(&edge->channels_lock); + + return ret; +} + +/** + * qcom_smd_open_channel() - claim additional channels on the same edge + * @sdev: smd_device handle + * @name: channel name + * @cb: callback method to use for incoming data + * + * Returns a channel handle on success, or -EPROBE_DEFER if the channel isn't + * ready. + */ +struct qcom_smd_channel *qcom_smd_open_channel(struct qcom_smd_device *sdev, + const char *name, + qcom_smd_cb_t cb) +{ + struct qcom_smd_channel *channel; + struct qcom_smd_edge *edge = sdev->channel->edge; + int ret; + + /* Wait up to HZ for the channel to appear */ + ret = wait_event_interruptible_timeout(edge->new_channel_event, + (channel = qcom_smd_find_channel(edge, name)) != NULL, + HZ); + if (!ret) + return ERR_PTR(-ETIMEDOUT); + + if (channel->state != SMD_CHANNEL_CLOSED) { + dev_err(&sdev->dev, "channel %s is busy\n", channel->name); + return ERR_PTR(-EBUSY); + } + + channel->qsdev = sdev; + ret = qcom_smd_channel_open(channel, cb); + if (ret) { + channel->qsdev = NULL; + return ERR_PTR(ret); + } + + /* + * Append the list of channel to the channels associated with the sdev + */ + list_add_tail(&channel->dev_list, &sdev->channel->dev_list); + + return channel; +} +EXPORT_SYMBOL(qcom_smd_open_channel); + /* * Allocate the qcom_smd_channel object for a newly found smd channel, * retrieving and validating the smem items involved. @@ -969,6 +1024,7 @@ static struct qcom_smd_channel *qcom_smd_create_channel(struct qcom_smd_edge *ed if (!channel) return ERR_PTR(-ENOMEM); + INIT_LIST_HEAD(&channel->dev_list); channel->edge = edge; channel->name = devm_kstrdup(smd->dev, name, GFP_KERNEL); if (!channel->name) @@ -1008,7 +1064,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; @@ -1031,8 +1087,9 @@ free_name_and_channel: * qcom_smd_create_channel() to create representations of these and add * them to the edge's list of channels. */ -static void qcom_discover_channels(struct qcom_smd_edge *edge) +static void qcom_channel_scan_worker(struct work_struct *work) { + struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, scan_work); struct qcom_smd_alloc_entry *alloc_tbl; struct qcom_smd_alloc_entry *entry; struct qcom_smd_channel *channel; @@ -1076,16 +1133,18 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge) if (IS_ERR(channel)) continue; - spin_lock_irqsave(&edge->channels_lock, flags); + write_lock_irqsave(&edge->channels_lock, flags); list_add(&channel->list, &edge->channels); - spin_unlock_irqrestore(&edge->channels_lock, flags); + write_unlock_irqrestore(&edge->channels_lock, flags); dev_dbg(smd->dev, "new channel found: '%s'\n", channel->name); set_bit(i, edge->allocated[tbl]); + + wake_up_interruptible(&edge->new_channel_event); } } - schedule_work(&edge->work); + schedule_work(&edge->state_work); } /* @@ -1093,29 +1152,22 @@ static void qcom_discover_channels(struct qcom_smd_edge *edge) * then scans all registered channels for state changes that should be handled * by creating or destroying smd client devices for the registered channels. * - * LOCKING: edge->channels_lock is not needed to be held during the traversal - * of the channels list as it's done synchronously with the only writer. + * LOCKING: edge->channels_lock only needs to cover the list operations, as the + * worker is killed before any channels are deallocated */ static void qcom_channel_state_worker(struct work_struct *work) { struct qcom_smd_channel *channel; struct qcom_smd_edge *edge = container_of(work, struct qcom_smd_edge, - work); + state_work); unsigned remote_state; /* - * Rescan smem if we have reason to belive that there are new channels. - */ - if (edge->need_rescan) { - edge->need_rescan = false; - qcom_discover_channels(edge); - } - - /* * Register a device for any closed channel where the remote processor * is showing interest in opening the channel. */ + read_lock(&edge->channels_lock); list_for_each_entry(channel, &edge->channels, list) { if (channel->state != SMD_CHANNEL_CLOSED) continue; @@ -1125,7 +1177,9 @@ static void qcom_channel_state_worker(struct work_struct *work) remote_state != SMD_CHANNEL_OPENED) continue; + read_unlock(&edge->channels_lock); qcom_smd_create_device(channel); + read_lock(&edge->channels_lock); } /* @@ -1142,8 +1196,11 @@ static void qcom_channel_state_worker(struct work_struct *work) remote_state == SMD_CHANNEL_OPENED) continue; + read_unlock(&edge->channels_lock); qcom_smd_destroy_device(channel); + read_lock(&edge->channels_lock); } + read_unlock(&edge->channels_lock); } /* @@ -1159,9 +1216,10 @@ static int qcom_smd_parse_edge(struct device *dev, int ret; INIT_LIST_HEAD(&edge->channels); - spin_lock_init(&edge->channels_lock); + rwlock_init(&edge->channels_lock); - INIT_WORK(&edge->work, qcom_channel_state_worker); + INIT_WORK(&edge->scan_work, qcom_channel_scan_worker); + INIT_WORK(&edge->state_work, qcom_channel_state_worker); edge->of_node = of_node_get(node); @@ -1190,7 +1248,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) { @@ -1244,13 +1306,13 @@ static int qcom_smd_probe(struct platform_device *pdev) for_each_available_child_of_node(pdev->dev.of_node, node) { edge = &smd->edges[i++]; edge->smd = smd; + init_waitqueue_head(&edge->new_channel_event); ret = qcom_smd_parse_edge(&pdev->dev, node, edge); if (ret) continue; - edge->need_rescan = true; - schedule_work(&edge->work); + schedule_work(&edge->scan_work); } platform_set_drvdata(pdev, smd); @@ -1273,8 +1335,10 @@ static int qcom_smd_remove(struct platform_device *pdev) edge = &smd->edges[i]; disable_irq(edge->irq); - cancel_work_sync(&edge->work); + cancel_work_sync(&edge->scan_work); + cancel_work_sync(&edge->state_work); + /* No need to lock here, because the writer is gone */ list_for_each_entry(channel, &edge->channels, list) { if (!channel->qsdev) continue; 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; } |