diff options
| author | Karthikeyan Ramasubramanian <kramasub@codeaurora.org> | 2016-01-07 17:35:17 -0700 |
|---|---|---|
| committer | Rohit Vaswani <rvaswani@codeaurora.org> | 2016-03-01 12:22:28 -0800 |
| commit | 82e136e3fd7e002e44426e47d4dcc51a9ee5240f (patch) | |
| tree | 1b897c03dcde46c4062fa020b6c35b51d3d1fae1 | |
| parent | 0e15f0e32b86d6b64578a0a1c0a7d982f2fce46a (diff) | |
soc: qcom: Add snapshot of SMD driver
This snapshot is taken as of msm-3.18 commit e70ad0cd (Promotion of
kernel.lnx.3.18-151201.)
Signed-off-by: Karthikeyan Ramasubramanian <kramasub@codeaurora.org>
| -rw-r--r-- | drivers/soc/qcom/Kconfig | 21 | ||||
| -rw-r--r-- | drivers/soc/qcom/Makefile | 1 | ||||
| -rw-r--r-- | drivers/soc/qcom/msm_smd.c | 3250 | ||||
| -rw-r--r-- | drivers/soc/qcom/smd_debug.c | 429 | ||||
| -rw-r--r-- | drivers/soc/qcom/smd_init_dt.c | 340 | ||||
| -rw-r--r-- | drivers/soc/qcom/smd_private.c | 333 | ||||
| -rw-r--r-- | drivers/soc/qcom/smd_private.h | 243 | ||||
| -rw-r--r-- | drivers/soc/qcom/smsm_debug.c | 330 | ||||
| -rw-r--r-- | include/soc/qcom/smd.h | 381 | ||||
| -rw-r--r-- | include/soc/qcom/smsm.h | 147 |
10 files changed, 5475 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 35c15f98af18..bfc443f7986a 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -10,6 +10,27 @@ config MSM_SMEM processors in the System on a Chip (SoC) which allows basic inter-processor communication. +config MSM_SMD + depends on MSM_SMEM + bool "MSM Shared Memory Driver (SMD)" + help + Support for the shared memory interprocessor communication protocol + which provides virual point to point serial channels between processes + on the apps processor and processes on other processors in the SoC. + Also includes support for the Shared Memory State Machine (SMSM) + protocol which provides a mechanism to publish single bit state + information to one or more processors in the SoC. + +config MSM_SMD_DEBUG + depends on MSM_SMD + bool "MSM SMD debug support" + help + Support for debugging SMD and SMSM communication between apps and + other processors in the SoC. Debug support primarily consists of + logs consisting of information such as what interrupts were processed, + what channels caused interrupt activity, and when internal state + change events occur. + config QCOM_GSBI tristate "QCOM General Serial Bus Interface" depends on ARCH_QCOM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 0fb212191797..9655c66c56cc 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_MSM_SMEM) += msm_smem.o smem_debug.o +obj-$(CONFIG_MSM_SMD) += msm_smd.o smd_debug.o smd_private.o smd_init_dt.o smsm_debug.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/msm_smd.c b/drivers/soc/qcom/msm_smd.c new file mode 100644 index 000000000000..6d4a57b9b385 --- /dev/null +++ b/drivers/soc/qcom/msm_smd.c @@ -0,0 +1,3250 @@ +/* drivers/soc/qcom/msm_smd.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2008-2016, The Linux Foundation. All rights reserved. + * Author: Brian Swetland <swetland@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/device.h> +#include <linux/wait.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/list.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/termios.h> +#include <linux/ctype.h> +#include <linux/remote_spinlock.h> +#include <linux/uaccess.h> +#include <linux/kfifo.h> +#include <linux/pm.h> +#include <linux/notifier.h> +#include <linux/suspend.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/ipc_logging.h> + +#include <soc/qcom/ramdump.h> +#include <soc/qcom/smd.h> +#include <soc/qcom/smem.h> +#include <soc/qcom/subsystem_notif.h> +#include <soc/qcom/subsystem_restart.h> + +#include "smd_private.h" +#include "smem_private.h" + +#define SMSM_SNAPSHOT_CNT 64 +#define SMSM_SNAPSHOT_SIZE ((SMSM_NUM_ENTRIES + 1) * 4 + sizeof(uint64_t)) +#define RSPIN_INIT_WAIT_MS 1000 +#define SMD_FIFO_FULL_RESERVE 4 +#define SMD_FIFO_ADDR_ALIGN_BYTES 3 + +uint32_t SMSM_NUM_ENTRIES = 8; +uint32_t SMSM_NUM_HOSTS = 3; + +/* Legacy SMSM interrupt notifications */ +#define LEGACY_MODEM_SMSM_MASK (SMSM_RESET | SMSM_INIT | SMSM_SMDINIT) + +struct smsm_shared_info { + uint32_t *state; + uint32_t *intr_mask; + uint32_t *intr_mux; +}; + +static struct smsm_shared_info smsm_info; +static struct kfifo smsm_snapshot_fifo; +static struct wakeup_source smsm_snapshot_ws; +static int smsm_snapshot_count; +static DEFINE_SPINLOCK(smsm_snapshot_count_lock); + +struct smsm_size_info_type { + uint32_t num_hosts; + uint32_t num_entries; + uint32_t reserved0; + uint32_t reserved1; +}; + +struct smsm_state_cb_info { + struct list_head cb_list; + uint32_t mask; + void *data; + void (*notify)(void *data, uint32_t old_state, uint32_t new_state); +}; + +struct smsm_state_info { + struct list_head callbacks; + uint32_t last_value; + uint32_t intr_mask_set; + uint32_t intr_mask_clear; +}; + +static irqreturn_t smsm_irq_handler(int irq, void *data); + +/* + * Interrupt configuration consists of static configuration for the supported + * processors that is done here along with interrupt configuration that is + * added by the separate initialization modules (device tree, platform data, or + * hard coded). + */ +static struct interrupt_config private_intr_config[NUM_SMD_SUBSYSTEMS] = { + [SMD_MODEM] = { + .smd.irq_handler = smd_modem_irq_handler, + .smsm.irq_handler = smsm_modem_irq_handler, + }, + [SMD_Q6] = { + .smd.irq_handler = smd_dsp_irq_handler, + .smsm.irq_handler = smsm_dsp_irq_handler, + }, + [SMD_DSPS] = { + .smd.irq_handler = smd_dsps_irq_handler, + .smsm.irq_handler = smsm_dsps_irq_handler, + }, + [SMD_WCNSS] = { + .smd.irq_handler = smd_wcnss_irq_handler, + .smsm.irq_handler = smsm_wcnss_irq_handler, + }, + [SMD_MODEM_Q6_FW] = { + .smd.irq_handler = smd_modemfw_irq_handler, + .smsm.irq_handler = NULL, /* does not support smsm */ + }, + [SMD_RPM] = { + .smd.irq_handler = smd_rpm_irq_handler, + .smsm.irq_handler = NULL, /* does not support smsm */ + }, +}; + +struct interrupt_stat interrupt_stats[NUM_SMD_SUBSYSTEMS]; + +#define SMSM_STATE_ADDR(entry) (smsm_info.state + entry) +#define SMSM_INTR_MASK_ADDR(entry, host) (smsm_info.intr_mask + \ + entry * SMSM_NUM_HOSTS + host) +#define SMSM_INTR_MUX_ADDR(entry) (smsm_info.intr_mux + entry) + +int msm_smd_debug_mask = MSM_SMD_POWER_INFO | MSM_SMD_INFO | + MSM_SMSM_POWER_INFO; +module_param_named(debug_mask, msm_smd_debug_mask, + int, S_IRUGO | S_IWUSR | S_IWGRP); +void *smd_log_ctx; +void *smsm_log_ctx; +#define NUM_LOG_PAGES 4 + +#define IPC_LOG_SMD(level, x...) do { \ + if (smd_log_ctx) \ + ipc_log_string(smd_log_ctx, x); \ + else \ + printk(level x); \ + } while (0) + +#define IPC_LOG_SMSM(level, x...) do { \ + if (smsm_log_ctx) \ + ipc_log_string(smsm_log_ctx, x); \ + else \ + printk(level x); \ + } while (0) + +#if defined(CONFIG_MSM_SMD_DEBUG) +#define SMD_DBG(x...) do { \ + if (msm_smd_debug_mask & MSM_SMD_DEBUG) \ + IPC_LOG_SMD(KERN_DEBUG, x); \ + } while (0) + +#define SMSM_DBG(x...) do { \ + if (msm_smd_debug_mask & MSM_SMSM_DEBUG) \ + IPC_LOG_SMSM(KERN_DEBUG, x); \ + } while (0) + +#define SMD_INFO(x...) do { \ + if (msm_smd_debug_mask & MSM_SMD_INFO) \ + IPC_LOG_SMD(KERN_INFO, x); \ + } while (0) + +#define SMSM_INFO(x...) do { \ + if (msm_smd_debug_mask & MSM_SMSM_INFO) \ + IPC_LOG_SMSM(KERN_INFO, x); \ + } while (0) + +#define SMD_POWER_INFO(x...) do { \ + if (msm_smd_debug_mask & MSM_SMD_POWER_INFO) \ + IPC_LOG_SMD(KERN_INFO, x); \ + } while (0) + +#define SMSM_POWER_INFO(x...) do { \ + if (msm_smd_debug_mask & MSM_SMSM_POWER_INFO) \ + IPC_LOG_SMSM(KERN_INFO, x); \ + } while (0) +#else +#define SMD_DBG(x...) do { } while (0) +#define SMSM_DBG(x...) do { } while (0) +#define SMD_INFO(x...) do { } while (0) +#define SMSM_INFO(x...) do { } while (0) +#define SMD_POWER_INFO(x...) do { } while (0) +#define SMSM_POWER_INFO(x...) do { } while (0) +#endif + +static void smd_fake_irq_handler(unsigned long arg); +static void smsm_cb_snapshot(uint32_t use_wakeup_source); + +static struct workqueue_struct *smsm_cb_wq; +static void notify_smsm_cb_clients_worker(struct work_struct *work); +static DECLARE_WORK(smsm_cb_work, notify_smsm_cb_clients_worker); +static DEFINE_MUTEX(smsm_lock); +static struct smsm_state_info *smsm_states; + +static int smd_stream_write_avail(struct smd_channel *ch); +static int smd_stream_read_avail(struct smd_channel *ch); + +static bool pid_is_on_edge(uint32_t edge_num, unsigned pid); + +static inline void smd_write_intr(unsigned int val, void __iomem *addr) +{ + wmb(); + __raw_writel(val, addr); +} + +/** + * smd_memcpy_to_fifo() - copy to SMD channel FIFO + * @dest: Destination address + * @src: Source address + * @num_bytes: Number of bytes to copy + * + * @return: Address of destination + * + * This function copies num_bytes from src to dest. This is used as the memcpy + * function to copy data to SMD FIFO in case the SMD FIFO is naturally aligned. + */ +static void *smd_memcpy_to_fifo(void *dest, const void *src, size_t num_bytes) +{ + + memcpy_toio(dest, src, num_bytes); + return dest; +} + +/** + * smd_memcpy_from_fifo() - copy from SMD channel FIFO + * @dest: Destination address + * @src: Source address + * @num_bytes: Number of bytes to copy + * + * @return: Address of destination + * + * This function copies num_bytes from src to dest. This is used as the memcpy + * function to copy data from SMD FIFO in case the SMD FIFO is naturally + * aligned. + */ +static void *smd_memcpy_from_fifo(void *dest, const void *src, size_t num_bytes) +{ + memcpy_fromio(dest, src, num_bytes); + return dest; +} + +/** + * smd_memcpy32_to_fifo() - Copy to SMD channel FIFO + * + * @dest: Destination address + * @src: Source address + * @num_bytes: Number of bytes to copy + * + * @return: On Success, address of destination + * + * This function copies num_bytes data from src to dest. This is used as the + * memcpy function to copy data to SMD FIFO in case the SMD FIFO is 4 byte + * aligned. + */ +static void *smd_memcpy32_to_fifo(void *dest, const void *src, size_t num_bytes) +{ + uint32_t *dest_local = (uint32_t *)dest; + uint32_t *src_local = (uint32_t *)src; + + BUG_ON(num_bytes & SMD_FIFO_ADDR_ALIGN_BYTES); + BUG_ON(!dest_local || + ((uintptr_t)dest_local & SMD_FIFO_ADDR_ALIGN_BYTES)); + BUG_ON(!src_local || + ((uintptr_t)src_local & SMD_FIFO_ADDR_ALIGN_BYTES)); + num_bytes /= sizeof(uint32_t); + + while (num_bytes--) + __raw_writel_no_log(*src_local++, dest_local++); + + return dest; +} + +/** + * smd_memcpy32_from_fifo() - Copy from SMD channel FIFO + * @dest: Destination address + * @src: Source address + * @num_bytes: Number of bytes to copy + * + * @return: On Success, destination address + * + * This function copies num_bytes data from SMD FIFO to dest. This is used as + * the memcpy function to copy data from SMD FIFO in case the SMD FIFO is 4 byte + * aligned. + */ +static void *smd_memcpy32_from_fifo(void *dest, const void *src, + size_t num_bytes) +{ + + uint32_t *dest_local = (uint32_t *)dest; + uint32_t *src_local = (uint32_t *)src; + + BUG_ON(num_bytes & SMD_FIFO_ADDR_ALIGN_BYTES); + BUG_ON(!dest_local || + ((uintptr_t)dest_local & SMD_FIFO_ADDR_ALIGN_BYTES)); + BUG_ON(!src_local || + ((uintptr_t)src_local & SMD_FIFO_ADDR_ALIGN_BYTES)); + num_bytes /= sizeof(uint32_t); + + while (num_bytes--) + *dest_local++ = __raw_readl_no_log(src_local++); + + return dest; +} + +static inline void log_notify(uint32_t subsystem, smd_channel_t *ch) +{ + const char *subsys = smd_edge_to_subsystem(subsystem); + + (void) subsys; + + if (!ch) + SMD_POWER_INFO("Apps->%s\n", subsys); + else + SMD_POWER_INFO( + "Apps->%s ch%d '%s': tx%d/rx%d %dr/%dw : %dr/%dw\n", + subsys, ch->n, ch->name, + ch->fifo_size - + (smd_stream_write_avail(ch) + 1), + smd_stream_read_avail(ch), + ch->half_ch->get_tail(ch->send), + ch->half_ch->get_head(ch->send), + ch->half_ch->get_tail(ch->recv), + ch->half_ch->get_head(ch->recv) + ); +} + +static inline void notify_modem_smd(smd_channel_t *ch) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_MODEM].smd; + + log_notify(SMD_APPS_MODEM, ch); + if (intr->out_base) { + ++interrupt_stats[SMD_MODEM].smd_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_dsp_smd(smd_channel_t *ch) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_Q6].smd; + + log_notify(SMD_APPS_QDSP, ch); + if (intr->out_base) { + ++interrupt_stats[SMD_Q6].smd_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_dsps_smd(smd_channel_t *ch) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_DSPS].smd; + + log_notify(SMD_APPS_DSPS, ch); + if (intr->out_base) { + ++interrupt_stats[SMD_DSPS].smd_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_wcnss_smd(struct smd_channel *ch) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_WCNSS].smd; + + log_notify(SMD_APPS_WCNSS, ch); + if (intr->out_base) { + ++interrupt_stats[SMD_WCNSS].smd_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_modemfw_smd(smd_channel_t *ch) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_MODEM_Q6_FW].smd; + + log_notify(SMD_APPS_Q6FW, ch); + if (intr->out_base) { + ++interrupt_stats[SMD_MODEM_Q6_FW].smd_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_rpm_smd(smd_channel_t *ch) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_RPM].smd; + + if (intr->out_base) { + log_notify(SMD_APPS_RPM, ch); + ++interrupt_stats[SMD_RPM].smd_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_modem_smsm(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_MODEM].smsm; + + SMSM_POWER_INFO("SMSM Apps->%s", "MODEM"); + + if (intr->out_base) { + ++interrupt_stats[SMD_MODEM].smsm_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_dsp_smsm(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_Q6].smsm; + + SMSM_POWER_INFO("SMSM Apps->%s", "ADSP"); + + if (intr->out_base) { + ++interrupt_stats[SMD_Q6].smsm_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_dsps_smsm(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_DSPS].smsm; + + SMSM_POWER_INFO("SMSM Apps->%s", "DSPS"); + + if (intr->out_base) { + ++interrupt_stats[SMD_DSPS].smsm_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static inline void notify_wcnss_smsm(void) +{ + static const struct interrupt_config_item *intr + = &private_intr_config[SMD_WCNSS].smsm; + + SMSM_POWER_INFO("SMSM Apps->%s", "WCNSS"); + + if (intr->out_base) { + ++interrupt_stats[SMD_WCNSS].smsm_out_count; + smd_write_intr(intr->out_bit_pos, + intr->out_base + intr->out_offset); + } +} + +static void notify_other_smsm(uint32_t smsm_entry, uint32_t notify_mask) +{ + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_MODEM)) + & notify_mask)) + notify_modem_smsm(); + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_Q6)) + & notify_mask)) + notify_dsp_smsm(); + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_WCNSS)) + & notify_mask)) { + notify_wcnss_smsm(); + } + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_DSPS)) + & notify_mask)) { + notify_dsps_smsm(); + } + + if (smsm_info.intr_mask && + (__raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)) + & notify_mask)) { + smsm_cb_snapshot(1); + } +} + +static int smsm_pm_notifier(struct notifier_block *nb, + unsigned long event, void *unused) +{ + switch (event) { + case PM_SUSPEND_PREPARE: + smsm_change_state(SMSM_APPS_STATE, SMSM_PROC_AWAKE, 0); + break; + + case PM_POST_SUSPEND: + smsm_change_state(SMSM_APPS_STATE, 0, SMSM_PROC_AWAKE); + break; + } + return NOTIFY_DONE; +} + +static struct notifier_block smsm_pm_nb = { + .notifier_call = smsm_pm_notifier, + .priority = 0, +}; + +/* the spinlock is used to synchronize between the + * irq handler and code that mutates the channel + * list or fiddles with channel state + */ +static DEFINE_SPINLOCK(smd_lock); +DEFINE_SPINLOCK(smem_lock); + +/* the mutex is used during open() and close() + * operations to avoid races while creating or + * destroying smd_channel structures + */ +static DEFINE_MUTEX(smd_creation_mutex); + +struct smd_shared { + struct smd_half_channel ch0; + struct smd_half_channel ch1; +}; + +struct smd_shared_word_access { + struct smd_half_channel_word_access ch0; + struct smd_half_channel_word_access ch1; +}; + +/** + * Maps edge type to local and remote processor ID's. + */ +static struct edge_to_pid edge_to_pids[] = { + [SMD_APPS_MODEM] = {SMD_APPS, SMD_MODEM, "modem"}, + [SMD_APPS_QDSP] = {SMD_APPS, SMD_Q6, "adsp"}, + [SMD_MODEM_QDSP] = {SMD_MODEM, SMD_Q6}, + [SMD_APPS_DSPS] = {SMD_APPS, SMD_DSPS, "dsps"}, + [SMD_MODEM_DSPS] = {SMD_MODEM, SMD_DSPS}, + [SMD_QDSP_DSPS] = {SMD_Q6, SMD_DSPS}, + [SMD_APPS_WCNSS] = {SMD_APPS, SMD_WCNSS, "wcnss"}, + [SMD_MODEM_WCNSS] = {SMD_MODEM, SMD_WCNSS}, + [SMD_QDSP_WCNSS] = {SMD_Q6, SMD_WCNSS}, + [SMD_DSPS_WCNSS] = {SMD_DSPS, SMD_WCNSS}, + [SMD_APPS_Q6FW] = {SMD_APPS, SMD_MODEM_Q6_FW}, + [SMD_MODEM_Q6FW] = {SMD_MODEM, SMD_MODEM_Q6_FW}, + [SMD_QDSP_Q6FW] = {SMD_Q6, SMD_MODEM_Q6_FW}, + [SMD_DSPS_Q6FW] = {SMD_DSPS, SMD_MODEM_Q6_FW}, + [SMD_WCNSS_Q6FW] = {SMD_WCNSS, SMD_MODEM_Q6_FW}, + [SMD_APPS_RPM] = {SMD_APPS, SMD_RPM}, + [SMD_MODEM_RPM] = {SMD_MODEM, SMD_RPM}, + [SMD_QDSP_RPM] = {SMD_Q6, SMD_RPM}, + [SMD_WCNSS_RPM] = {SMD_WCNSS, SMD_RPM}, + [SMD_TZ_RPM] = {SMD_TZ, SMD_RPM}, +}; + +struct restart_notifier_block { + unsigned processor; + char *name; + struct notifier_block nb; +}; + +static struct platform_device loopback_tty_pdev = {.name = "LOOPBACK_TTY"}; + +static LIST_HEAD(smd_ch_closed_list); +static LIST_HEAD(smd_ch_closing_list); +static LIST_HEAD(smd_ch_to_close_list); + +struct remote_proc_info { + unsigned remote_pid; + unsigned free_space; + struct work_struct probe_work; + struct list_head ch_list; + /* 2 total supported tables of channels */ + unsigned char ch_allocated[SMEM_NUM_SMD_STREAM_CHANNELS * 2]; + bool skip_pil; +}; + +static struct remote_proc_info remote_info[NUM_SMD_SUBSYSTEMS]; + +static void finalize_channel_close_fn(struct work_struct *work); +static DECLARE_WORK(finalize_channel_close_work, finalize_channel_close_fn); +static struct workqueue_struct *channel_close_wq; + +#define PRI_ALLOC_TBL 1 +#define SEC_ALLOC_TBL 2 +static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm, int table_id, + struct remote_proc_info *r_info); + +static bool smd_edge_inited(int edge) +{ + return edge_to_pids[edge].initialized; +} + +/* on smp systems, the probe might get called from multiple cores, + hence use a lock */ +static DEFINE_MUTEX(smd_probe_lock); + +/** + * scan_alloc_table - Scans a specified SMD channel allocation table in SMEM for + * newly created channels that need to be made locally + * visable + * + * @shared: pointer to the table array in SMEM + * @smd_ch_allocated: pointer to an array indicating already allocated channels + * @table_id: identifier for this channel allocation table + * @num_entries: number of entries in this allocation table + * @r_info: pointer to the info structure of the remote proc we care about + * + * The smd_probe_lock must be locked by the calling function. Shared and + * smd_ch_allocated are assumed to be valid pointers. + */ +static void scan_alloc_table(struct smd_alloc_elm *shared, + char *smd_ch_allocated, + int table_id, + unsigned num_entries, + struct remote_proc_info *r_info) +{ + unsigned n; + uint32_t type; + + for (n = 0; n < num_entries; n++) { + if (smd_ch_allocated[n]) + continue; + + /* + * channel should be allocated only if APPS processor is + * involved + */ + type = SMD_CHANNEL_TYPE(shared[n].type); + if (!pid_is_on_edge(type, SMD_APPS) || + !pid_is_on_edge(type, r_info->remote_pid)) + continue; + if (!shared[n].ref_count) + continue; + if (!shared[n].name[0]) + continue; + + if (!smd_edge_inited(type)) { + SMD_INFO( + "Probe skipping proc %d, tbl %d, ch %d, edge not inited\n", + r_info->remote_pid, table_id, n); + continue; + } + + if (!smd_alloc_channel(&shared[n], table_id, r_info)) + smd_ch_allocated[n] = 1; + else + SMD_INFO( + "Probe skipping proc %d, tbl %d, ch %d, not allocated\n", + r_info->remote_pid, table_id, n); + } +} + +static void smd_channel_probe_now(struct remote_proc_info *r_info) +{ + struct smd_alloc_elm *shared; + unsigned tbl_size; + + shared = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size, + r_info->remote_pid, 0); + + if (!shared) { + pr_err("%s: allocation table not initialized\n", __func__); + return; + } + + mutex_lock(&smd_probe_lock); + + scan_alloc_table(shared, r_info->ch_allocated, PRI_ALLOC_TBL, + tbl_size / sizeof(*shared), + r_info); + + shared = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size, + r_info->remote_pid, 0); + if (shared) + scan_alloc_table(shared, + &(r_info->ch_allocated[SMEM_NUM_SMD_STREAM_CHANNELS]), + SEC_ALLOC_TBL, + tbl_size / sizeof(*shared), + r_info); + + mutex_unlock(&smd_probe_lock); +} + +/** + * smd_channel_probe_worker() - Scan for newly created SMD channels and init + * local structures so the channels are visable to + * local clients + * + * @work: work_struct corresponding to an instance of this function running on + * a workqueue. + */ +static void smd_channel_probe_worker(struct work_struct *work) +{ + struct remote_proc_info *r_info; + + r_info = container_of(work, struct remote_proc_info, probe_work); + + smd_channel_probe_now(r_info); +} + +/** + * get_remote_ch() - gathers remote channel info + * + * @shared2: Pointer to v2 shared channel structure + * @type: Edge type + * @pid: Processor ID of processor on edge + * @remote_ch: Channel that belongs to processor @pid + * @is_word_access_ch: Bool, is this a word aligned access channel + * + * @returns: 0 on success, error code on failure + */ +static int get_remote_ch(void *shared2, + uint32_t type, uint32_t pid, + void **remote_ch, + int is_word_access_ch + ) +{ + if (!remote_ch || !shared2 || !pid_is_on_edge(type, pid) || + !pid_is_on_edge(type, SMD_APPS)) + return -EINVAL; + + if (is_word_access_ch) + *remote_ch = + &((struct smd_shared_word_access *)(shared2))->ch1; + else + *remote_ch = &((struct smd_shared *)(shared2))->ch1; + + return 0; +} + +/** + * smd_remote_ss_to_edge() - return edge type from remote ss type + * @name: remote subsystem name + * + * Returns the edge type connected between the local subsystem(APPS) + * and remote subsystem @name. + */ +int smd_remote_ss_to_edge(const char *name) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(edge_to_pids); ++i) { + if (edge_to_pids[i].subsys_name[0] != 0x0) { + if (!strncmp(edge_to_pids[i].subsys_name, name, + strlen(name))) + return i; + } + } + + return -EINVAL; +} +EXPORT_SYMBOL(smd_remote_ss_to_edge); + +/** + * smd_edge_to_pil_str - Returns the PIL string used to load the remote side of + * the indicated edge. + * + * @type - Edge definition + * @returns - The PIL string to load the remove side of @type or NULL if the + * PIL string does not exist. + */ +const char *smd_edge_to_pil_str(uint32_t type) +{ + const char *pil_str = NULL; + + if (type < ARRAY_SIZE(edge_to_pids)) { + if (!edge_to_pids[type].initialized) + return ERR_PTR(-EPROBE_DEFER); + if (!remote_info[smd_edge_to_remote_pid(type)].skip_pil) { + pil_str = edge_to_pids[type].subsys_name; + if (pil_str[0] == 0x0) + pil_str = NULL; + } + } + return pil_str; +} +EXPORT_SYMBOL(smd_edge_to_pil_str); + +/* + * Returns a pointer to the subsystem name or NULL if no + * subsystem name is available. + * + * @type - Edge definition + */ +const char *smd_edge_to_subsystem(uint32_t type) +{ + const char *subsys = NULL; + + if (type < ARRAY_SIZE(edge_to_pids)) { + subsys = edge_to_pids[type].subsys_name; + if (subsys[0] == 0x0) + subsys = NULL; + if (!edge_to_pids[type].initialized) + subsys = ERR_PTR(-EPROBE_DEFER); + } + return subsys; +} +EXPORT_SYMBOL(smd_edge_to_subsystem); + +/* + * Returns a pointer to the subsystem name given the + * remote processor ID. + * subsystem is not necessarily PIL-loadable + * + * @pid Remote processor ID + * @returns Pointer to subsystem name or NULL if not found + */ +const char *smd_pid_to_subsystem(uint32_t pid) +{ + const char *subsys = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(edge_to_pids); ++i) { + if (pid == edge_to_pids[i].remote_pid) { + if (!edge_to_pids[i].initialized) { + subsys = ERR_PTR(-EPROBE_DEFER); + break; + } + if (edge_to_pids[i].subsys_name[0] != 0x0) { + subsys = edge_to_pids[i].subsys_name; + break; + } else if (pid == SMD_RPM) { + subsys = "rpm"; + break; + } + } + } + + return subsys; +} +EXPORT_SYMBOL(smd_pid_to_subsystem); + +static void smd_reset_edge(void *void_ch, unsigned new_state, + int is_word_access_ch) +{ + if (is_word_access_ch) { + struct smd_half_channel_word_access *ch = + (struct smd_half_channel_word_access *)(void_ch); + if (ch->state != SMD_SS_CLOSED) { + ch->state = new_state; + ch->fDSR = 0; + ch->fCTS = 0; + ch->fCD = 0; + ch->fSTATE = 1; + } + } else { + struct smd_half_channel *ch = + (struct smd_half_channel *)(void_ch); + if (ch->state != SMD_SS_CLOSED) { + ch->state = new_state; + ch->fDSR = 0; + ch->fCTS = 0; + ch->fCD = 0; + ch->fSTATE = 1; + } + } +} + +/** + * smd_channel_reset_state() - find channels in an allocation table and set them + * to the specified state + * + * @shared: Pointer to the allocation table to scan + * @table_id: ID of the table + * @new_state: New state that channels should be set to + * @pid: Processor ID of the remote processor for the channels + * @num_entries: Number of entries in the table + * + * Scan the indicated table for channels between Apps and @pid. If a valid + * channel is found, set the remote side of the channel to @new_state. + */ +static void smd_channel_reset_state(struct smd_alloc_elm *shared, int table_id, + unsigned new_state, unsigned pid, unsigned num_entries) +{ + unsigned n; + void *shared2; + uint32_t type; + void *remote_ch; + int is_word_access; + unsigned base_id; + + switch (table_id) { + case PRI_ALLOC_TBL: + base_id = SMEM_SMD_BASE_ID; + break; + case SEC_ALLOC_TBL: + base_id = SMEM_SMD_BASE_ID_2; + break; + default: + SMD_INFO("%s: invalid table_id:%d\n", __func__, table_id); + return; + } + + for (n = 0; n < num_entries; n++) { + if (!shared[n].ref_count) + continue; + if (!shared[n].name[0]) + continue; + + type = SMD_CHANNEL_TYPE(shared[n].type); + is_word_access = is_word_access_ch(type); + if (is_word_access) + shared2 = smem_find(base_id + n, + sizeof(struct smd_shared_word_access), pid, + 0); + else + shared2 = smem_find(base_id + n, + sizeof(struct smd_shared), pid, 0); + if (!shared2) + continue; + + if (!get_remote_ch(shared2, type, pid, + &remote_ch, is_word_access)) + smd_reset_edge(remote_ch, new_state, is_word_access); + } +} + +/** + * pid_is_on_edge() - checks to see if the processor with id pid is on the + * edge specified by edge_num + * + * @edge_num: the number of the edge which is being tested + * @pid: the id of the processor being tested + * + * @returns: true if on edge, false otherwise + */ +static bool pid_is_on_edge(uint32_t edge_num, unsigned pid) +{ + struct edge_to_pid edge; + + if (edge_num >= ARRAY_SIZE(edge_to_pids)) + return 0; + + edge = edge_to_pids[edge_num]; + return (edge.local_pid == pid || edge.remote_pid == pid); +} + +void smd_channel_reset(uint32_t restart_pid) +{ + struct smd_alloc_elm *shared_pri; + struct smd_alloc_elm *shared_sec; + unsigned long flags; + unsigned pri_size; + unsigned sec_size; + + SMD_POWER_INFO("%s: starting reset\n", __func__); + + shared_pri = smem_get_entry(ID_CH_ALLOC_TBL, &pri_size, restart_pid, 0); + if (!shared_pri) { + pr_err("%s: allocation table not initialized\n", __func__); + return; + } + shared_sec = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &sec_size, + restart_pid, 0); + + /* reset SMSM entry */ + if (smsm_info.state) { + writel_relaxed(0, SMSM_STATE_ADDR(restart_pid)); + + /* restart SMSM init handshake */ + if (restart_pid == SMSM_MODEM) { + smsm_change_state(SMSM_APPS_STATE, + SMSM_INIT | SMSM_SMD_LOOPBACK | SMSM_RESET, + 0); + } + + /* notify SMSM processors */ + smsm_irq_handler(0, 0); + notify_modem_smsm(); + notify_dsp_smsm(); + notify_dsps_smsm(); + notify_wcnss_smsm(); + } + + /* change all remote states to CLOSING */ + mutex_lock(&smd_probe_lock); + spin_lock_irqsave(&smd_lock, flags); + smd_channel_reset_state(shared_pri, PRI_ALLOC_TBL, SMD_SS_CLOSING, + restart_pid, pri_size / sizeof(*shared_pri)); + if (shared_sec) + smd_channel_reset_state(shared_sec, SEC_ALLOC_TBL, + SMD_SS_CLOSING, restart_pid, + sec_size / sizeof(*shared_sec)); + spin_unlock_irqrestore(&smd_lock, flags); + mutex_unlock(&smd_probe_lock); + + mb(); + smd_fake_irq_handler(0); + + /* change all remote states to CLOSED */ + mutex_lock(&smd_probe_lock); + spin_lock_irqsave(&smd_lock, flags); + smd_channel_reset_state(shared_pri, PRI_ALLOC_TBL, SMD_SS_CLOSED, + restart_pid, pri_size / sizeof(*shared_pri)); + if (shared_sec) + smd_channel_reset_state(shared_sec, SEC_ALLOC_TBL, + SMD_SS_CLOSED, restart_pid, + sec_size / sizeof(*shared_sec)); + spin_unlock_irqrestore(&smd_lock, flags); + mutex_unlock(&smd_probe_lock); + + mb(); + smd_fake_irq_handler(0); + + SMD_POWER_INFO("%s: finished reset\n", __func__); +} + +/* how many bytes are available for reading */ +static int smd_stream_read_avail(struct smd_channel *ch) +{ + unsigned head = ch->half_ch->get_head(ch->recv); + unsigned tail = ch->half_ch->get_tail(ch->recv); + unsigned fifo_size = ch->fifo_size; + unsigned bytes_avail = head - tail; + + if (head < tail) + bytes_avail += fifo_size; + + BUG_ON(bytes_avail >= fifo_size); + return bytes_avail; +} + +/* how many bytes we are free to write */ +static int smd_stream_write_avail(struct smd_channel *ch) +{ + unsigned head = ch->half_ch->get_head(ch->send); + unsigned tail = ch->half_ch->get_tail(ch->send); + unsigned fifo_size = ch->fifo_size; + unsigned bytes_avail = tail - head; + + if (tail <= head) + bytes_avail += fifo_size; + if (bytes_avail < SMD_FIFO_FULL_RESERVE) + bytes_avail = 0; + else + bytes_avail -= SMD_FIFO_FULL_RESERVE; + + BUG_ON(bytes_avail >= fifo_size); + return bytes_avail; +} + +static int smd_packet_read_avail(struct smd_channel *ch) +{ + if (ch->current_packet) { + int n = smd_stream_read_avail(ch); + if (n > ch->current_packet) + n = ch->current_packet; + return n; + } else { + return 0; + } +} + +static int smd_packet_write_avail(struct smd_channel *ch) +{ + int n = smd_stream_write_avail(ch); + return n > SMD_HEADER_SIZE ? n - SMD_HEADER_SIZE : 0; +} + +static int ch_is_open(struct smd_channel *ch) +{ + return (ch->half_ch->get_state(ch->recv) == SMD_SS_OPENED || + ch->half_ch->get_state(ch->recv) == SMD_SS_FLUSHING) + && (ch->half_ch->get_state(ch->send) == SMD_SS_OPENED); +} + +/* provide a pointer and length to readable data in the fifo */ +static unsigned ch_read_buffer(struct smd_channel *ch, void **ptr) +{ + unsigned head = ch->half_ch->get_head(ch->recv); + unsigned tail = ch->half_ch->get_tail(ch->recv); + unsigned fifo_size = ch->fifo_size; + + BUG_ON(fifo_size >= SZ_1M); + BUG_ON(head >= fifo_size); + BUG_ON(tail >= fifo_size); + BUG_ON(OVERFLOW_ADD_UNSIGNED(uintptr_t, (uintptr_t)ch->recv_data, + tail)); + *ptr = (void *) (ch->recv_data + tail); + if (tail <= head) + return head - tail; + else + return fifo_size - tail; +} + +static int read_intr_blocked(struct smd_channel *ch) +{ + return ch->half_ch->get_fBLOCKREADINTR(ch->recv); +} + +/* advance the fifo read pointer after data from ch_read_buffer is consumed */ +static void ch_read_done(struct smd_channel *ch, unsigned count) +{ + unsigned tail = ch->half_ch->get_tail(ch->recv); + unsigned fifo_size = ch->fifo_size; + + BUG_ON(count > smd_stream_read_avail(ch)); + + tail += count; + if (tail >= fifo_size) + tail -= fifo_size; + ch->half_ch->set_tail(ch->recv, tail); + wmb(); + ch->half_ch->set_fTAIL(ch->send, 1); +} + +/* basic read interface to ch_read_{buffer,done} used + * by smd_*_read() and update_packet_state() + * will read-and-discard if the _data pointer is null + */ +static int ch_read(struct smd_channel *ch, void *_data, int len) +{ + void *ptr; + unsigned n; + unsigned char *data = _data; + int orig_len = len; + + while (len > 0) { + n = ch_read_buffer(ch, &ptr); + if (n == 0) + break; + + if (n > len) + n = len; + if (_data) + ch->read_from_fifo(data, ptr, n); + + data += n; + len -= n; + ch_read_done(ch, n); + } + + return orig_len - len; +} + +static void update_stream_state(struct smd_channel *ch) +{ + /* streams have no special state requiring updating */ +} + +static void update_packet_state(struct smd_channel *ch) +{ + unsigned hdr[5]; + int r; + const char *peripheral = NULL; + + /* can't do anything if we're in the middle of a packet */ + while (ch->current_packet == 0) { + /* discard 0 length packets if any */ + + /* don't bother unless we can get the full header */ + if (smd_stream_read_avail(ch) < SMD_HEADER_SIZE) + return; + + r = ch_read(ch, hdr, SMD_HEADER_SIZE); + BUG_ON(r != SMD_HEADER_SIZE); + + ch->current_packet = hdr[0]; + if (ch->current_packet > (uint32_t)INT_MAX) { + pr_err("%s: Invalid packet size of %d bytes detected. Edge: %d, Channel : %s, RPTR: %d, WPTR: %d", + __func__, ch->current_packet, ch->type, + ch->name, ch->half_ch->get_tail(ch->recv), + ch->half_ch->get_head(ch->recv)); + peripheral = smd_edge_to_pil_str(ch->type); + if (peripheral) { + if (subsystem_restart(peripheral) < 0) + BUG(); + } else { + BUG(); + } + } + } +} + +/** + * ch_write_buffer() - Provide a pointer and length for the next segment of + * free space in the FIFO. + * @ch: channel + * @ptr: Address to pointer for the next segment write + * @returns: Maximum size that can be written until the FIFO is either full + * or the end of the FIFO has been reached. + * + * The returned pointer and length are passed to memcpy, so the next segment is + * defined as either the space available between the read index (tail) and the + * write index (head) or the space available to the end of the FIFO. + */ +static unsigned ch_write_buffer(struct smd_channel *ch, void **ptr) +{ + unsigned head = ch->half_ch->get_head(ch->send); + unsigned tail = ch->half_ch->get_tail(ch->send); + unsigned fifo_size = ch->fifo_size; + + BUG_ON(fifo_size >= SZ_1M); + BUG_ON(head >= fifo_size); + BUG_ON(tail >= fifo_size); + BUG_ON(OVERFLOW_ADD_UNSIGNED(uintptr_t, (uintptr_t)ch->send_data, + head)); + + *ptr = (void *) (ch->send_data + head); + if (head < tail) { + return tail - head - SMD_FIFO_FULL_RESERVE; + } else { + if (tail < SMD_FIFO_FULL_RESERVE) + return fifo_size + tail - head + - SMD_FIFO_FULL_RESERVE; + else + return fifo_size - head; + } +} + +/* advace the fifo write pointer after freespace + * from ch_write_buffer is filled + */ +static void ch_write_done(struct smd_channel *ch, unsigned count) +{ + unsigned head = ch->half_ch->get_head(ch->send); + unsigned fifo_size = ch->fifo_size; + + BUG_ON(count > smd_stream_write_avail(ch)); + head += count; + if (head >= fifo_size) + head -= fifo_size; + ch->half_ch->set_head(ch->send, head); + wmb(); + ch->half_ch->set_fHEAD(ch->send, 1); +} + +static void ch_set_state(struct smd_channel *ch, unsigned n) +{ + if (n == SMD_SS_OPENED) { + ch->half_ch->set_fDSR(ch->send, 1); + ch->half_ch->set_fCTS(ch->send, 1); + ch->half_ch->set_fCD(ch->send, 1); + } else { + ch->half_ch->set_fDSR(ch->send, 0); + ch->half_ch->set_fCTS(ch->send, 0); + ch->half_ch->set_fCD(ch->send, 0); + } + ch->half_ch->set_state(ch->send, n); + ch->half_ch->set_fSTATE(ch->send, 1); + ch->notify_other_cpu(ch); +} + +/** + * do_smd_probe() - Look for newly created SMD channels a specific processor + * + * @remote_pid: remote processor id of the proc that may have created channels + */ +static void do_smd_probe(unsigned remote_pid) +{ + unsigned free_space; + + free_space = smem_get_free_space(remote_pid); + if (free_space != remote_info[remote_pid].free_space) { + remote_info[remote_pid].free_space = free_space; + schedule_work(&remote_info[remote_pid].probe_work); + } +} + +static void remote_processed_close(struct smd_channel *ch) +{ + /* The remote side has observed our close, we can allow a reopen */ + list_move(&ch->ch_list, &smd_ch_to_close_list); + queue_work(channel_close_wq, &finalize_channel_close_work); +} + +static void smd_state_change(struct smd_channel *ch, + unsigned last, unsigned next) +{ + ch->last_state = next; + + SMD_INFO("SMD: ch %d %d -> %d\n", ch->n, last, next); + + switch (next) { + case SMD_SS_OPENING: + if (last == SMD_SS_OPENED && + ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED) { + /* We missed the CLOSING and CLOSED states */ + remote_processed_close(ch); + } else if (ch->half_ch->get_state(ch->send) == SMD_SS_CLOSING || + ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED) { + ch->half_ch->set_tail(ch->recv, 0); + ch->half_ch->set_head(ch->send, 0); + ch->half_ch->set_fBLOCKREADINTR(ch->send, 0); + ch->current_packet = 0; + ch_set_state(ch, SMD_SS_OPENING); + } + break; + case SMD_SS_OPENED: + if (ch->half_ch->get_state(ch->send) == SMD_SS_OPENING) { + ch_set_state(ch, SMD_SS_OPENED); + ch->notify(ch->priv, SMD_EVENT_OPEN); + } + break; + case SMD_SS_FLUSHING: + case SMD_SS_RESET: + /* we should force them to close? */ + break; + case SMD_SS_CLOSED: + if (ch->half_ch->get_state(ch->send) == SMD_SS_OPENED) { + ch_set_state(ch, SMD_SS_CLOSING); + ch->pending_pkt_sz = 0; + ch->notify(ch->priv, SMD_EVENT_CLOSE); + } + /* We missed the CLOSING state */ + if (ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED) + remote_processed_close(ch); + break; + case SMD_SS_CLOSING: + if (ch->half_ch->get_state(ch->send) == SMD_SS_CLOSED) + remote_processed_close(ch); + break; + } +} + +static void handle_smd_irq_closing_list(void) +{ + unsigned long flags; + struct smd_channel *ch; + struct smd_channel *index; + unsigned tmp; + + spin_lock_irqsave(&smd_lock, flags); + list_for_each_entry_safe(ch, index, &smd_ch_closing_list, ch_list) { + if (ch->half_ch->get_fSTATE(ch->recv)) + ch->half_ch->set_fSTATE(ch->recv, 0); + tmp = ch->half_ch->get_state(ch->recv); + if (tmp != ch->last_state) + smd_state_change(ch, ch->last_state, tmp); + } + spin_unlock_irqrestore(&smd_lock, flags); +} + +static void handle_smd_irq(struct remote_proc_info *r_info, + void (*notify)(smd_channel_t *ch)) +{ + unsigned long flags; + struct smd_channel *ch; + unsigned ch_flags; + unsigned tmp; + unsigned char state_change; + struct list_head *list; + + list = &r_info->ch_list; + + spin_lock_irqsave(&smd_lock, flags); + list_for_each_entry(ch, list, ch_list) { + state_change = 0; + ch_flags = 0; + if (ch_is_open(ch)) { + if (ch->half_ch->get_fHEAD(ch->recv)) { + ch->half_ch->set_fHEAD(ch->recv, 0); + ch_flags |= 1; + } + if (ch->half_ch->get_fTAIL(ch->recv)) { + ch->half_ch->set_fTAIL(ch->recv, 0); + ch_flags |= 2; + } + if (ch->half_ch->get_fSTATE(ch->recv)) { + ch->half_ch->set_fSTATE(ch->recv, 0); + ch_flags |= 4; + } + } + tmp = ch->half_ch->get_state(ch->recv); + if (tmp != ch->last_state) { + SMD_POWER_INFO("SMD ch%d '%s' State change %d->%d\n", + ch->n, ch->name, ch->last_state, tmp); + smd_state_change(ch, ch->last_state, tmp); + state_change = 1; + } + if (ch_flags & 0x3) { + ch->update_state(ch); + SMD_POWER_INFO( + "SMD ch%d '%s' Data event 0x%x tx%d/rx%d %dr/%dw : %dr/%dw\n", + ch->n, ch->name, + ch_flags, + ch->fifo_size - + (smd_stream_write_avail(ch) + 1), + smd_stream_read_avail(ch), + ch->half_ch->get_tail(ch->send), + ch->half_ch->get_head(ch->send), + ch->half_ch->get_tail(ch->recv), + ch->half_ch->get_head(ch->recv) + ); + ch->notify(ch->priv, SMD_EVENT_DATA); + } + if (ch_flags & 0x4 && !state_change) { + SMD_POWER_INFO("SMD ch%d '%s' State update\n", + ch->n, ch->name); + ch->notify(ch->priv, SMD_EVENT_STATUS); + } + } + spin_unlock_irqrestore(&smd_lock, flags); + do_smd_probe(r_info->remote_pid); +} + +static inline void log_irq(uint32_t subsystem) +{ + const char *subsys = smd_edge_to_subsystem(subsystem); + + (void) subsys; + + SMD_POWER_INFO("SMD Int %s->Apps\n", subsys); +} + +irqreturn_t smd_modem_irq_handler(int irq, void *data) +{ + if (unlikely(!edge_to_pids[SMD_APPS_MODEM].initialized)) + return IRQ_HANDLED; + log_irq(SMD_APPS_MODEM); + ++interrupt_stats[SMD_MODEM].smd_in_count; + handle_smd_irq(&remote_info[SMD_MODEM], notify_modem_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +irqreturn_t smd_dsp_irq_handler(int irq, void *data) +{ + if (unlikely(!edge_to_pids[SMD_APPS_QDSP].initialized)) + return IRQ_HANDLED; + log_irq(SMD_APPS_QDSP); + ++interrupt_stats[SMD_Q6].smd_in_count; + handle_smd_irq(&remote_info[SMD_Q6], notify_dsp_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +irqreturn_t smd_dsps_irq_handler(int irq, void *data) +{ + if (unlikely(!edge_to_pids[SMD_APPS_DSPS].initialized)) + return IRQ_HANDLED; + log_irq(SMD_APPS_DSPS); + ++interrupt_stats[SMD_DSPS].smd_in_count; + handle_smd_irq(&remote_info[SMD_DSPS], notify_dsps_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +irqreturn_t smd_wcnss_irq_handler(int irq, void *data) +{ + if (unlikely(!edge_to_pids[SMD_APPS_WCNSS].initialized)) + return IRQ_HANDLED; + log_irq(SMD_APPS_WCNSS); + ++interrupt_stats[SMD_WCNSS].smd_in_count; + handle_smd_irq(&remote_info[SMD_WCNSS], notify_wcnss_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +irqreturn_t smd_modemfw_irq_handler(int irq, void *data) +{ + if (unlikely(!edge_to_pids[SMD_APPS_Q6FW].initialized)) + return IRQ_HANDLED; + log_irq(SMD_APPS_Q6FW); + ++interrupt_stats[SMD_MODEM_Q6_FW].smd_in_count; + handle_smd_irq(&remote_info[SMD_MODEM_Q6_FW], notify_modemfw_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +irqreturn_t smd_rpm_irq_handler(int irq, void *data) +{ + if (unlikely(!edge_to_pids[SMD_APPS_RPM].initialized)) + return IRQ_HANDLED; + log_irq(SMD_APPS_RPM); + ++interrupt_stats[SMD_RPM].smd_in_count; + handle_smd_irq(&remote_info[SMD_RPM], notify_rpm_smd); + handle_smd_irq_closing_list(); + return IRQ_HANDLED; +} + +static void smd_fake_irq_handler(unsigned long arg) +{ + handle_smd_irq(&remote_info[SMD_MODEM], notify_modem_smd); + handle_smd_irq(&remote_info[SMD_Q6], notify_dsp_smd); + handle_smd_irq(&remote_info[SMD_DSPS], notify_dsps_smd); + handle_smd_irq(&remote_info[SMD_WCNSS], notify_wcnss_smd); + handle_smd_irq(&remote_info[SMD_MODEM_Q6_FW], notify_modemfw_smd); + handle_smd_irq(&remote_info[SMD_RPM], notify_rpm_smd); + handle_smd_irq_closing_list(); +} + +static int smd_is_packet(struct smd_alloc_elm *alloc_elm) +{ + if (SMD_XFER_TYPE(alloc_elm->type) == 1) + return 0; + else if (SMD_XFER_TYPE(alloc_elm->type) == 2) + return 1; + + panic("Unsupported SMD xfer type: %d name:%s edge:%d\n", + SMD_XFER_TYPE(alloc_elm->type), + alloc_elm->name, + SMD_CHANNEL_TYPE(alloc_elm->type)); +} + +static int smd_stream_write(smd_channel_t *ch, const void *_data, int len, + bool intr_ntfy) +{ + void *ptr; + const unsigned char *buf = _data; + unsigned xfer; + int orig_len = len; + + SMD_DBG("smd_stream_write() %d -> ch%d\n", len, ch->n); + if (len < 0) + return -EINVAL; + else if (len == 0) + return 0; + + while ((xfer = ch_write_buffer(ch, &ptr)) != 0) { + if (!ch_is_open(ch)) { + len = orig_len; + break; + } + if (xfer > len) + xfer = len; + + ch->write_to_fifo(ptr, buf, xfer); + ch_write_done(ch, xfer); + len -= xfer; + buf += xfer; + if (len == 0) + break; + } + + if (orig_len - len && intr_ntfy) + ch->notify_other_cpu(ch); + + return orig_len - len; +} + +static int smd_packet_write(smd_channel_t *ch, const void *_data, int len, + bool intr_ntfy) +{ + int ret; + unsigned hdr[5]; + + SMD_DBG("smd_packet_write() %d -> ch%d\n", len, ch->n); + if (len < 0) + return -EINVAL; + else if (len == 0) + return 0; + + if (smd_stream_write_avail(ch) < (len + SMD_HEADER_SIZE)) + return -ENOMEM; + + hdr[0] = len; + hdr[1] = hdr[2] = hdr[3] = hdr[4] = 0; + + + ret = smd_stream_write(ch, hdr, sizeof(hdr), false); + if (ret < 0 || ret != sizeof(hdr)) { + SMD_DBG("%s failed to write pkt header: %d returned\n", + __func__, ret); + return -EFAULT; + } + + + ret = smd_stream_write(ch, _data, len, true); + if (ret < 0 || ret != len) { + SMD_DBG("%s failed to write pkt data: %d returned\n", + __func__, ret); + return ret; + } + + return len; +} + +static int smd_stream_read(smd_channel_t *ch, void *data, int len) +{ + int r; + + if (len < 0) + return -EINVAL; + + r = ch_read(ch, data, len); + if (r > 0) + if (!read_intr_blocked(ch)) + ch->notify_other_cpu(ch); + + return r; +} + +static int smd_packet_read(smd_channel_t *ch, void *data, int len) +{ + unsigned long flags; + int r; + + if (len < 0) + return -EINVAL; + + if (ch->current_packet > (uint32_t)INT_MAX) { + pr_err("%s: Invalid packet size for Edge %d and Channel %s", + __func__, ch->type, ch->name); + return -EFAULT; + } + + if (len > ch->current_packet) + len = ch->current_packet; + + r = ch_read(ch, data, len); + if (r > 0) + if (!read_intr_blocked(ch)) + ch->notify_other_cpu(ch); + + spin_lock_irqsave(&smd_lock, flags); + ch->current_packet -= r; + update_packet_state(ch); + spin_unlock_irqrestore(&smd_lock, flags); + + return r; +} + +static int smd_packet_read_from_cb(smd_channel_t *ch, void *data, int len) +{ + int r; + + if (len < 0) + return -EINVAL; + + if (ch->current_packet > (uint32_t)INT_MAX) { + pr_err("%s: Invalid packet size for Edge %d and Channel %s", + __func__, ch->type, ch->name); + return -EFAULT; + } + + if (len > ch->current_packet) + len = ch->current_packet; + + r = ch_read(ch, data, len); + if (r > 0) + if (!read_intr_blocked(ch)) + ch->notify_other_cpu(ch); + + ch->current_packet -= r; + update_packet_state(ch); + + return r; +} + +/** + * smd_alloc_v2() - Init local channel structure with information stored in SMEM + * + * @ch: pointer to the local structure for this channel + * @table_id: the id of the table this channel resides in. 1 = first table, 2 = + * second table, etc + * @r_info: pointer to the info structure of the remote proc for this channel + * @returns: -EINVAL for failure; 0 for success + * + * ch must point to an allocated instance of struct smd_channel that is zeroed + * out, and has the n and type members already initialized to the correct values + */ +static int smd_alloc(struct smd_channel *ch, int table_id, + struct remote_proc_info *r_info) +{ + void *buffer; + unsigned buffer_sz; + unsigned base_id; + unsigned fifo_id; + + switch (table_id) { + case PRI_ALLOC_TBL: + base_id = SMEM_SMD_BASE_ID; + fifo_id = SMEM_SMD_FIFO_BASE_ID; + break; + case SEC_ALLOC_TBL: + base_id = SMEM_SMD_BASE_ID_2; + fifo_id = SMEM_SMD_FIFO_BASE_ID_2; + break; + default: + SMD_INFO("Invalid table_id:%d passed to smd_alloc\n", table_id); + return -EINVAL; + } + + if (is_word_access_ch(ch->type)) { + struct smd_shared_word_access *shared2; + shared2 = smem_find(base_id + ch->n, sizeof(*shared2), + r_info->remote_pid, 0); + if (!shared2) { + SMD_INFO("smem_find failed ch=%d\n", ch->n); + return -EINVAL; + } + ch->send = &shared2->ch0; + ch->recv = &shared2->ch1; + } else { + struct smd_shared *shared2; + shared2 = smem_find(base_id + ch->n, sizeof(*shared2), + r_info->remote_pid, 0); + if (!shared2) { + SMD_INFO("smem_find failed ch=%d\n", ch->n); + return -EINVAL; + } + ch->send = &shared2->ch0; + ch->recv = &shared2->ch1; + } + ch->half_ch = get_half_ch_funcs(ch->type); + + buffer = smem_get_entry(fifo_id + ch->n, &buffer_sz, + r_info->remote_pid, 0); + if (!buffer) { + SMD_INFO("smem_get_entry failed\n"); + return -EINVAL; + } + + /* buffer must be a multiple of 32 size */ + if ((buffer_sz & (SZ_32 - 1)) != 0) { + SMD_INFO("Buffer size: %u not multiple of 32\n", buffer_sz); + return -EINVAL; + } + buffer_sz /= 2; + ch->send_data = buffer; + ch->recv_data = buffer + buffer_sz; + ch->fifo_size = buffer_sz; + + return 0; +} + +/** + * smd_alloc_channel() - Create and init local structures for a newly allocated + * SMD channel + * + * @alloc_elm: the allocation element stored in SMEM for this channel + * @table_id: the id of the table this channel resides in. 1 = first table, 2 = + * seconds table, etc + * @r_info: pointer to the info structure of the remote proc for this channel + * @returns: error code for failure; 0 for success + */ +static int smd_alloc_channel(struct smd_alloc_elm *alloc_elm, int table_id, + struct remote_proc_info *r_info) +{ + struct smd_channel *ch; + + ch = kzalloc(sizeof(struct smd_channel), GFP_KERNEL); + if (ch == 0) { + pr_err("smd_alloc_channel() out of memory\n"); + return -ENOMEM; + } + ch->n = alloc_elm->cid; + ch->type = SMD_CHANNEL_TYPE(alloc_elm->type); + + if (smd_alloc(ch, table_id, r_info)) { + kfree(ch); + return -ENODEV; + } + + /* probe_worker guarentees ch->type will be a valid type */ + if (ch->type == SMD_APPS_MODEM) + ch->notify_other_cpu = notify_modem_smd; + else if (ch->type == SMD_APPS_QDSP) + ch->notify_other_cpu = notify_dsp_smd; + else if (ch->type == SMD_APPS_DSPS) + ch->notify_other_cpu = notify_dsps_smd; + else if (ch->type == SMD_APPS_WCNSS) + ch->notify_other_cpu = notify_wcnss_smd; + else if (ch->type == SMD_APPS_Q6FW) + ch->notify_other_cpu = notify_modemfw_smd; + else if (ch->type == SMD_APPS_RPM) + ch->notify_other_cpu = notify_rpm_smd; + + if (smd_is_packet(alloc_elm)) { + ch->read = smd_packet_read; + ch->write = smd_packet_write; + ch->read_avail = smd_packet_read_avail; + ch->write_avail = smd_packet_write_avail; + ch->update_state = update_packet_state; + ch->read_from_cb = smd_packet_read_from_cb; + ch->is_pkt_ch = 1; + } else { + ch->read = smd_stream_read; + ch->write = smd_stream_write; + ch->read_avail = smd_stream_read_avail; + ch->write_avail = smd_stream_write_avail; + ch->update_state = update_stream_state; + ch->read_from_cb = smd_stream_read; + } + + if (is_word_access_ch(ch->type)) { + ch->read_from_fifo = smd_memcpy32_from_fifo; + ch->write_to_fifo = smd_memcpy32_to_fifo; + } else { + ch->read_from_fifo = smd_memcpy_from_fifo; + ch->write_to_fifo = smd_memcpy_to_fifo; + } + + smd_memcpy_from_fifo(ch->name, alloc_elm->name, SMD_MAX_CH_NAME_LEN); + ch->name[SMD_MAX_CH_NAME_LEN-1] = 0; + + ch->pdev.name = ch->name; + ch->pdev.id = ch->type; + + SMD_INFO("smd_alloc_channel() '%s' cid=%d\n", + ch->name, ch->n); + + mutex_lock(&smd_creation_mutex); + list_add(&ch->ch_list, &smd_ch_closed_list); + mutex_unlock(&smd_creation_mutex); + + platform_device_register(&ch->pdev); + if (!strncmp(ch->name, "LOOPBACK", 8) && ch->type == SMD_APPS_MODEM) { + /* create a platform driver to be used by smd_tty driver + * so that it can access the loopback port + */ + loopback_tty_pdev.id = ch->type; + platform_device_register(&loopback_tty_pdev); + } + return 0; +} + +static void do_nothing_notify(void *priv, unsigned flags) +{ +} + +static void finalize_channel_close_fn(struct work_struct *work) +{ + unsigned long flags; + struct smd_channel *ch; + struct smd_channel *index; + + mutex_lock(&smd_creation_mutex); + spin_lock_irqsave(&smd_lock, flags); + list_for_each_entry_safe(ch, index, &smd_ch_to_close_list, ch_list) { + list_del(&ch->ch_list); + list_add(&ch->ch_list, &smd_ch_closed_list); + ch->notify(ch->priv, SMD_EVENT_REOPEN_READY); + ch->notify = do_nothing_notify; + } + spin_unlock_irqrestore(&smd_lock, flags); + mutex_unlock(&smd_creation_mutex); +} + +struct smd_channel *smd_get_channel(const char *name, uint32_t type) +{ + struct smd_channel *ch; + + mutex_lock(&smd_creation_mutex); + list_for_each_entry(ch, &smd_ch_closed_list, ch_list) { + if (!strcmp(name, ch->name) && + (type == ch->type)) { + list_del(&ch->ch_list); + mutex_unlock(&smd_creation_mutex); + return ch; + } + } + mutex_unlock(&smd_creation_mutex); + + return NULL; +} + +int smd_named_open_on_edge(const char *name, uint32_t edge, + smd_channel_t **_ch, + void *priv, void (*notify)(void *, unsigned)) +{ + struct smd_channel *ch; + unsigned long flags; + + if (edge >= SMD_NUM_TYPE) { + pr_err("%s: edge:%d is invalid\n", __func__, edge); + return -EINVAL; + } + + if (!smd_edge_inited(edge)) { + SMD_INFO("smd_open() before smd_init()\n"); + return -EPROBE_DEFER; + } + + SMD_DBG("smd_open('%s', %p, %p)\n", name, priv, notify); + + ch = smd_get_channel(name, edge); + if (!ch) { + spin_lock_irqsave(&smd_lock, flags); + /* check opened list for port */ + list_for_each_entry(ch, + &remote_info[edge_to_pids[edge].remote_pid].ch_list, + ch_list) { + if (!strcmp(name, ch->name)) { + /* channel is already open */ + spin_unlock_irqrestore(&smd_lock, flags); + SMD_DBG("smd_open: channel '%s' already open\n", + ch->name); + return -EBUSY; + } + } + + /* check closing list for port */ + list_for_each_entry(ch, &smd_ch_closing_list, ch_list) { + if (!strncmp(name, ch->name, 20) && + (edge == ch->type)) { + /* channel exists, but is being closed */ + spin_unlock_irqrestore(&smd_lock, flags); + return -EAGAIN; + } + } + + /* check closing workqueue list for port */ + list_for_each_entry(ch, &smd_ch_to_close_list, ch_list) { + if (!strncmp(name, ch->name, 20) && + (edge == ch->type)) { + /* channel exists, but is being closed */ + spin_unlock_irqrestore(&smd_lock, flags); + return -EAGAIN; + } + } + spin_unlock_irqrestore(&smd_lock, flags); + + /* one final check to handle closing->closed race condition */ + ch = smd_get_channel(name, edge); + if (!ch) + return -ENODEV; + } + + if (ch->half_ch->get_fSTATE(ch->send)) { + /* remote side hasn't acknowledged our last state transition */ + SMD_INFO("%s: ch %d valid, waiting for remote to ack state\n", + __func__, ch->n); + msleep(250); + if (ch->half_ch->get_fSTATE(ch->send)) + SMD_INFO("%s: ch %d - no remote ack, continuing\n", + __func__, ch->n); + } + + if (notify == 0) + notify = do_nothing_notify; + + ch->notify = notify; + ch->current_packet = 0; + ch->last_state = SMD_SS_CLOSED; + ch->priv = priv; + + *_ch = ch; + + SMD_DBG("smd_open: opening '%s'\n", ch->name); + + spin_lock_irqsave(&smd_lock, flags); + list_add(&ch->ch_list, + &remote_info[edge_to_pids[ch->type].remote_pid].ch_list); + + SMD_DBG("%s: opening ch %d\n", __func__, ch->n); + + smd_state_change(ch, ch->last_state, SMD_SS_OPENING); + + spin_unlock_irqrestore(&smd_lock, flags); + + return 0; +} +EXPORT_SYMBOL(smd_named_open_on_edge); + +int smd_close(smd_channel_t *ch) +{ + unsigned long flags; + bool was_opened; + + if (ch == 0) + return -EINVAL; + + SMD_INFO("smd_close(%s)\n", ch->name); + + spin_lock_irqsave(&smd_lock, flags); + list_del(&ch->ch_list); + + was_opened = ch->half_ch->get_state(ch->recv) == SMD_SS_OPENED; + ch_set_state(ch, SMD_SS_CLOSED); + + if (was_opened) { + list_add(&ch->ch_list, &smd_ch_closing_list); + spin_unlock_irqrestore(&smd_lock, flags); + } else { + spin_unlock_irqrestore(&smd_lock, flags); + ch->notify = do_nothing_notify; + mutex_lock(&smd_creation_mutex); + list_add(&ch->ch_list, &smd_ch_closed_list); + mutex_unlock(&smd_creation_mutex); + } + + return 0; +} +EXPORT_SYMBOL(smd_close); + +int smd_write_start(smd_channel_t *ch, int len) +{ + int ret; + unsigned hdr[5]; + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + if (!ch->is_pkt_ch) { + pr_err("%s: non-packet channel specified\n", __func__); + return -EACCES; + } + if (len < 1) { + pr_err("%s: invalid length: %d\n", __func__, len); + return -EINVAL; + } + + if (ch->pending_pkt_sz) { + pr_err("%s: packet of size: %d in progress\n", __func__, + ch->pending_pkt_sz); + return -EBUSY; + } + ch->pending_pkt_sz = len; + + if (smd_stream_write_avail(ch) < (SMD_HEADER_SIZE)) { + ch->pending_pkt_sz = 0; + SMD_DBG("%s: no space to write packet header\n", __func__); + return -EAGAIN; + } + + hdr[0] = len; + hdr[1] = hdr[2] = hdr[3] = hdr[4] = 0; + + + ret = smd_stream_write(ch, hdr, sizeof(hdr), true); + if (ret < 0 || ret != sizeof(hdr)) { + ch->pending_pkt_sz = 0; + pr_err("%s: packet header failed to write\n", __func__); + return -EPERM; + } + return 0; +} +EXPORT_SYMBOL(smd_write_start); + +int smd_write_segment(smd_channel_t *ch, const void *data, int len) +{ + int bytes_written; + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + if (len < 1) { + pr_err("%s: invalid length: %d\n", __func__, len); + return -EINVAL; + } + + if (!ch->pending_pkt_sz) { + pr_err("%s: no transaction in progress\n", __func__); + return -ENOEXEC; + } + if (ch->pending_pkt_sz - len < 0) { + pr_err("%s: segment of size: %d will make packet go over length\n", + __func__, len); + return -EINVAL; + } + + bytes_written = smd_stream_write(ch, data, len, true); + + ch->pending_pkt_sz -= bytes_written; + + return bytes_written; +} +EXPORT_SYMBOL(smd_write_segment); + +int smd_write_end(smd_channel_t *ch) +{ + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + if (ch->pending_pkt_sz) { + pr_err("%s: current packet not completely written\n", __func__); + return -E2BIG; + } + + return 0; +} +EXPORT_SYMBOL(smd_write_end); + +int smd_write_segment_avail(smd_channel_t *ch) +{ + int n; + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + if (!ch->is_pkt_ch) { + pr_err("%s: non-packet channel specified\n", __func__); + return -ENODEV; + } + + n = smd_stream_write_avail(ch); + + /* pkt hdr already written, no need to reserve space for it */ + if (ch->pending_pkt_sz) + return n; + + return n > SMD_HEADER_SIZE ? n - SMD_HEADER_SIZE : 0; +} +EXPORT_SYMBOL(smd_write_segment_avail); + +int smd_read(smd_channel_t *ch, void *data, int len) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return ch->read(ch, data, len); +} +EXPORT_SYMBOL(smd_read); + +int smd_read_from_cb(smd_channel_t *ch, void *data, int len) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return ch->read_from_cb(ch, data, len); +} +EXPORT_SYMBOL(smd_read_from_cb); + +int smd_write(smd_channel_t *ch, const void *data, int len) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return ch->pending_pkt_sz ? -EBUSY : ch->write(ch, data, len, true); +} +EXPORT_SYMBOL(smd_write); + +int smd_read_avail(smd_channel_t *ch) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + if (ch->current_packet > (uint32_t)INT_MAX) { + pr_err("%s: Invalid packet size for Edge %d and Channel %s", + __func__, ch->type, ch->name); + return -EFAULT; + } + return ch->read_avail(ch); +} +EXPORT_SYMBOL(smd_read_avail); + +int smd_write_avail(smd_channel_t *ch) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return ch->write_avail(ch); +} +EXPORT_SYMBOL(smd_write_avail); + +void smd_enable_read_intr(smd_channel_t *ch) +{ + if (ch) + ch->half_ch->set_fBLOCKREADINTR(ch->send, 0); +} +EXPORT_SYMBOL(smd_enable_read_intr); + +void smd_disable_read_intr(smd_channel_t *ch) +{ + if (ch) + ch->half_ch->set_fBLOCKREADINTR(ch->send, 1); +} +EXPORT_SYMBOL(smd_disable_read_intr); + +/** + * Enable/disable receive interrupts for the remote processor used by a + * particular channel. + * @ch: open channel handle to use for the edge + * @mask: 1 = mask interrupts; 0 = unmask interrupts + * @cpumask cpumask for the next cpu scheduled to be woken up + * @returns: 0 for success; < 0 for failure + * + * Note that this enables/disables all interrupts from the remote subsystem for + * all channels. As such, it should be used with care and only for specific + * use cases such as power-collapse sequencing. + */ +int smd_mask_receive_interrupt(smd_channel_t *ch, bool mask, + const struct cpumask *cpumask) +{ + struct irq_chip *irq_chip; + struct irq_data *irq_data; + struct interrupt_config_item *int_cfg; + + if (!ch) + return -EINVAL; + + if (ch->type >= ARRAY_SIZE(edge_to_pids)) + return -ENODEV; + + int_cfg = &private_intr_config[edge_to_pids[ch->type].remote_pid].smd; + + if (int_cfg->irq_id < 0) + return -ENODEV; + + irq_chip = irq_get_chip(int_cfg->irq_id); + if (!irq_chip) + return -ENODEV; + + irq_data = irq_get_irq_data(int_cfg->irq_id); + if (!irq_data) + return -ENODEV; + + if (mask) { + SMD_POWER_INFO("SMD Masking interrupts from %s\n", + edge_to_pids[ch->type].subsys_name); + irq_chip->irq_mask(irq_data); + if (cpumask) + irq_set_affinity(int_cfg->irq_id, cpumask); + } else { + SMD_POWER_INFO("SMD Unmasking interrupts from %s\n", + edge_to_pids[ch->type].subsys_name); + irq_chip->irq_unmask(irq_data); + } + + return 0; +} +EXPORT_SYMBOL(smd_mask_receive_interrupt); + +int smd_cur_packet_size(smd_channel_t *ch) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + if (ch->current_packet > (uint32_t)INT_MAX) { + pr_err("%s: Invalid packet size for Edge %d and Channel %s", + __func__, ch->type, ch->name); + return -EFAULT; + } + return ch->current_packet; +} +EXPORT_SYMBOL(smd_cur_packet_size); + +int smd_tiocmget(smd_channel_t *ch) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + return (ch->half_ch->get_fDSR(ch->recv) ? TIOCM_DSR : 0) | + (ch->half_ch->get_fCTS(ch->recv) ? TIOCM_CTS : 0) | + (ch->half_ch->get_fCD(ch->recv) ? TIOCM_CD : 0) | + (ch->half_ch->get_fRI(ch->recv) ? TIOCM_RI : 0) | + (ch->half_ch->get_fCTS(ch->send) ? TIOCM_RTS : 0) | + (ch->half_ch->get_fDSR(ch->send) ? TIOCM_DTR : 0); +} +EXPORT_SYMBOL(smd_tiocmget); + +/* this api will be called while holding smd_lock */ +int +smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + if (set & TIOCM_DTR) + ch->half_ch->set_fDSR(ch->send, 1); + + if (set & TIOCM_RTS) + ch->half_ch->set_fCTS(ch->send, 1); + + if (clear & TIOCM_DTR) + ch->half_ch->set_fDSR(ch->send, 0); + + if (clear & TIOCM_RTS) + ch->half_ch->set_fCTS(ch->send, 0); + + ch->half_ch->set_fSTATE(ch->send, 1); + barrier(); + ch->notify_other_cpu(ch); + + return 0; +} +EXPORT_SYMBOL(smd_tiocmset_from_cb); + +int smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + unsigned long flags; + + if (!ch) { + pr_err("%s: Invalid channel specified\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&smd_lock, flags); + smd_tiocmset_from_cb(ch, set, clear); + spin_unlock_irqrestore(&smd_lock, flags); + + return 0; +} +EXPORT_SYMBOL(smd_tiocmset); + +int smd_is_pkt_avail(smd_channel_t *ch) +{ + unsigned long flags; + + if (!ch || !ch->is_pkt_ch) + return -EINVAL; + + if (ch->current_packet) + return 1; + + spin_lock_irqsave(&smd_lock, flags); + update_packet_state(ch); + spin_unlock_irqrestore(&smd_lock, flags); + + return ch->current_packet ? 1 : 0; +} +EXPORT_SYMBOL(smd_is_pkt_avail); + +static int smsm_cb_init(void) +{ + struct smsm_state_info *state_info; + int n; + int ret = 0; + + smsm_states = kmalloc(sizeof(struct smsm_state_info)*SMSM_NUM_ENTRIES, + GFP_KERNEL); + + if (!smsm_states) { + pr_err("%s: SMSM init failed\n", __func__); + return -ENOMEM; + } + + smsm_cb_wq = create_singlethread_workqueue("smsm_cb_wq"); + if (!smsm_cb_wq) { + pr_err("%s: smsm_cb_wq creation failed\n", __func__); + kfree(smsm_states); + return -EFAULT; + } + + mutex_lock(&smsm_lock); + for (n = 0; n < SMSM_NUM_ENTRIES; n++) { + state_info = &smsm_states[n]; + state_info->last_value = __raw_readl(SMSM_STATE_ADDR(n)); + state_info->intr_mask_set = 0x0; + state_info->intr_mask_clear = 0x0; + INIT_LIST_HEAD(&state_info->callbacks); + } + mutex_unlock(&smsm_lock); + + return ret; +} + +static int smsm_init(void) +{ + int i; + struct smsm_size_info_type *smsm_size_info; + unsigned long flags; + unsigned long j_start; + static int first = 1; + remote_spinlock_t *remote_spinlock; + + if (!first) + return 0; + first = 0; + + /* Verify that remote spinlock is not deadlocked */ + remote_spinlock = smem_get_remote_spinlock(); + j_start = jiffies; + while (!remote_spin_trylock_irqsave(remote_spinlock, flags)) { + if (jiffies_to_msecs(jiffies - j_start) > RSPIN_INIT_WAIT_MS) { + panic("%s: Remote processor %d will not release spinlock\n", + __func__, remote_spin_owner(remote_spinlock)); + } + } + remote_spin_unlock_irqrestore(remote_spinlock, flags); + + smsm_size_info = smem_find(SMEM_SMSM_SIZE_INFO, + sizeof(struct smsm_size_info_type), 0, + SMEM_ANY_HOST_FLAG); + if (smsm_size_info) { + SMSM_NUM_ENTRIES = smsm_size_info->num_entries; + SMSM_NUM_HOSTS = smsm_size_info->num_hosts; + } + + i = kfifo_alloc(&smsm_snapshot_fifo, + sizeof(uint32_t) * SMSM_NUM_ENTRIES * SMSM_SNAPSHOT_CNT, + GFP_KERNEL); + if (i) { + pr_err("%s: SMSM state fifo alloc failed %d\n", __func__, i); + return i; + } + wakeup_source_init(&smsm_snapshot_ws, "smsm_snapshot"); + + if (!smsm_info.state) { + smsm_info.state = smem_alloc(ID_SHARED_STATE, + SMSM_NUM_ENTRIES * + sizeof(uint32_t), 0, + SMEM_ANY_HOST_FLAG); + + if (smsm_info.state) + __raw_writel(0, SMSM_STATE_ADDR(SMSM_APPS_STATE)); + } + + if (!smsm_info.intr_mask) { + smsm_info.intr_mask = smem_alloc(SMEM_SMSM_CPU_INTR_MASK, + SMSM_NUM_ENTRIES * + SMSM_NUM_HOSTS * + sizeof(uint32_t), 0, + SMEM_ANY_HOST_FLAG); + + if (smsm_info.intr_mask) { + for (i = 0; i < SMSM_NUM_ENTRIES; i++) + __raw_writel(0x0, + SMSM_INTR_MASK_ADDR(i, SMSM_APPS)); + + /* Configure legacy modem bits */ + __raw_writel(LEGACY_MODEM_SMSM_MASK, + SMSM_INTR_MASK_ADDR(SMSM_MODEM_STATE, + SMSM_APPS)); + } + } + + i = smsm_cb_init(); + if (i) + return i; + + wmb(); + + smsm_pm_notifier(&smsm_pm_nb, PM_POST_SUSPEND, NULL); + i = register_pm_notifier(&smsm_pm_nb); + if (i) + pr_err("%s: power state notif error %d\n", __func__, i); + + return 0; +} + +static void smsm_cb_snapshot(uint32_t use_wakeup_source) +{ + int n; + uint32_t new_state; + unsigned long flags; + int ret; + uint64_t timestamp; + + timestamp = sched_clock(); + ret = kfifo_avail(&smsm_snapshot_fifo); + if (ret < SMSM_SNAPSHOT_SIZE) { + pr_err("%s: SMSM snapshot full %d\n", __func__, ret); + return; + } + + /* + * To avoid a race condition with notify_smsm_cb_clients_worker, the + * following sequence must be followed: + * 1) increment snapshot count + * 2) insert data into FIFO + * + * Potentially in parallel, the worker: + * a) verifies >= 1 snapshots are in FIFO + * b) processes snapshot + * c) decrements reference count + * + * This order ensures that 1 will always occur before abc. + */ + if (use_wakeup_source) { + spin_lock_irqsave(&smsm_snapshot_count_lock, flags); + if (smsm_snapshot_count == 0) { + SMSM_POWER_INFO("SMSM snapshot wake lock\n"); + __pm_stay_awake(&smsm_snapshot_ws); + } + ++smsm_snapshot_count; + spin_unlock_irqrestore(&smsm_snapshot_count_lock, flags); + } + + /* queue state entries */ + for (n = 0; n < SMSM_NUM_ENTRIES; n++) { + new_state = __raw_readl(SMSM_STATE_ADDR(n)); + + ret = kfifo_in(&smsm_snapshot_fifo, + &new_state, sizeof(new_state)); + if (ret != sizeof(new_state)) { + pr_err("%s: SMSM snapshot failure %d\n", __func__, ret); + goto restore_snapshot_count; + } + } + + ret = kfifo_in(&smsm_snapshot_fifo, ×tamp, sizeof(timestamp)); + if (ret != sizeof(timestamp)) { + pr_err("%s: SMSM snapshot failure %d\n", __func__, ret); + goto restore_snapshot_count; + } + + /* queue wakelock usage flag */ + ret = kfifo_in(&smsm_snapshot_fifo, + &use_wakeup_source, sizeof(use_wakeup_source)); + if (ret != sizeof(use_wakeup_source)) { + pr_err("%s: SMSM snapshot failure %d\n", __func__, ret); + goto restore_snapshot_count; + } + + queue_work(smsm_cb_wq, &smsm_cb_work); + return; + +restore_snapshot_count: + if (use_wakeup_source) { + spin_lock_irqsave(&smsm_snapshot_count_lock, flags); + if (smsm_snapshot_count) { + --smsm_snapshot_count; + if (smsm_snapshot_count == 0) { + SMSM_POWER_INFO("SMSM snapshot wake unlock\n"); + __pm_relax(&smsm_snapshot_ws); + } + } else { + pr_err("%s: invalid snapshot count\n", __func__); + } + spin_unlock_irqrestore(&smsm_snapshot_count_lock, flags); + } +} + +static irqreturn_t smsm_irq_handler(int irq, void *data) +{ + unsigned long flags; + + spin_lock_irqsave(&smem_lock, flags); + if (!smsm_info.state) { + SMSM_INFO("<SM NO STATE>\n"); + } else { + unsigned old_apps, apps; + unsigned modm = __raw_readl(SMSM_STATE_ADDR(SMSM_MODEM_STATE)); + + old_apps = apps = __raw_readl(SMSM_STATE_ADDR(SMSM_APPS_STATE)); + + SMSM_DBG("<SM %08x %08x>\n", apps, modm); + if (modm & SMSM_RESET) { + pr_err("SMSM: Modem SMSM state changed to SMSM_RESET.\n"); + } else if (modm & SMSM_INIT) { + if (!(apps & SMSM_INIT)) + apps |= SMSM_INIT; + if (modm & SMSM_SMDINIT) + apps |= SMSM_SMDINIT; + } + + if (old_apps != apps) { + SMSM_DBG("<SM %08x NOTIFY>\n", apps); + __raw_writel(apps, SMSM_STATE_ADDR(SMSM_APPS_STATE)); + notify_other_smsm(SMSM_APPS_STATE, (old_apps ^ apps)); + } + + smsm_cb_snapshot(1); + } + spin_unlock_irqrestore(&smem_lock, flags); + return IRQ_HANDLED; +} + +irqreturn_t smsm_modem_irq_handler(int irq, void *data) +{ + SMSM_POWER_INFO("SMSM Int Modem->Apps\n"); + ++interrupt_stats[SMD_MODEM].smsm_in_count; + return smsm_irq_handler(irq, data); +} + +irqreturn_t smsm_dsp_irq_handler(int irq, void *data) +{ + SMSM_POWER_INFO("SMSM Int LPASS->Apps\n"); + ++interrupt_stats[SMD_Q6].smsm_in_count; + return smsm_irq_handler(irq, data); +} + +irqreturn_t smsm_dsps_irq_handler(int irq, void *data) +{ + SMSM_POWER_INFO("SMSM Int DSPS->Apps\n"); + ++interrupt_stats[SMD_DSPS].smsm_in_count; + return smsm_irq_handler(irq, data); +} + +irqreturn_t smsm_wcnss_irq_handler(int irq, void *data) +{ + SMSM_POWER_INFO("SMSM Int WCNSS->Apps\n"); + ++interrupt_stats[SMD_WCNSS].smsm_in_count; + return smsm_irq_handler(irq, data); +} + +/* + * Changes the global interrupt mask. The set and clear masks are re-applied + * every time the global interrupt mask is updated for callback registration + * and de-registration. + * + * The clear mask is applied first, so if a bit is set to 1 in both the clear + * mask and the set mask, the result will be that the interrupt is set. + * + * @smsm_entry SMSM entry to change + * @clear_mask 1 = clear bit, 0 = no-op + * @set_mask 1 = set bit, 0 = no-op + * + * @returns 0 for success, < 0 for error + */ +int smsm_change_intr_mask(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask) +{ + uint32_t old_mask, new_mask; + unsigned long flags; + + if (smsm_entry >= SMSM_NUM_ENTRIES) { + pr_err("smsm_change_state: Invalid entry %d\n", + smsm_entry); + return -EINVAL; + } + + if (!smsm_info.intr_mask) { + pr_err("smsm_change_intr_mask <SM NO STATE>\n"); + return -EIO; + } + + spin_lock_irqsave(&smem_lock, flags); + smsm_states[smsm_entry].intr_mask_clear = clear_mask; + smsm_states[smsm_entry].intr_mask_set = set_mask; + + old_mask = __raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); + new_mask = (old_mask & ~clear_mask) | set_mask; + __raw_writel(new_mask, SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); + + wmb(); + spin_unlock_irqrestore(&smem_lock, flags); + + return 0; +} +EXPORT_SYMBOL(smsm_change_intr_mask); + +int smsm_get_intr_mask(uint32_t smsm_entry, uint32_t *intr_mask) +{ + if (smsm_entry >= SMSM_NUM_ENTRIES) { + pr_err("smsm_change_state: Invalid entry %d\n", + smsm_entry); + return -EINVAL; + } + + if (!smsm_info.intr_mask) { + pr_err("smsm_change_intr_mask <SM NO STATE>\n"); + return -EIO; + } + + *intr_mask = __raw_readl(SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); + return 0; +} +EXPORT_SYMBOL(smsm_get_intr_mask); + +int smsm_change_state(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask) +{ + unsigned long flags; + uint32_t old_state, new_state; + + if (smsm_entry >= SMSM_NUM_ENTRIES) { + pr_err("smsm_change_state: Invalid entry %d", + smsm_entry); + return -EINVAL; + } + + if (!smsm_info.state) { + pr_err("smsm_change_state <SM NO STATE>\n"); + return -EIO; + } + spin_lock_irqsave(&smem_lock, flags); + + old_state = __raw_readl(SMSM_STATE_ADDR(smsm_entry)); + new_state = (old_state & ~clear_mask) | set_mask; + __raw_writel(new_state, SMSM_STATE_ADDR(smsm_entry)); + SMSM_POWER_INFO("%s %d:%08x->%08x", __func__, smsm_entry, + old_state, new_state); + notify_other_smsm(SMSM_APPS_STATE, (old_state ^ new_state)); + + spin_unlock_irqrestore(&smem_lock, flags); + + return 0; +} +EXPORT_SYMBOL(smsm_change_state); + +uint32_t smsm_get_state(uint32_t smsm_entry) +{ + uint32_t rv = 0; + + /* needs interface change to return error code */ + if (smsm_entry >= SMSM_NUM_ENTRIES) { + pr_err("smsm_change_state: Invalid entry %d", + smsm_entry); + return 0; + } + + if (!smsm_info.state) + pr_err("smsm_get_state <SM NO STATE>\n"); + else + rv = __raw_readl(SMSM_STATE_ADDR(smsm_entry)); + + return rv; +} +EXPORT_SYMBOL(smsm_get_state); + +/** + * Performs SMSM callback client notifiction. + */ +void notify_smsm_cb_clients_worker(struct work_struct *work) +{ + struct smsm_state_cb_info *cb_info; + struct smsm_state_info *state_info; + int n; + uint32_t new_state; + uint32_t state_changes; + uint32_t use_wakeup_source; + int ret; + unsigned long flags; + uint64_t t_snapshot; + uint64_t t_start; + unsigned long nanosec_rem; + + while (kfifo_len(&smsm_snapshot_fifo) >= SMSM_SNAPSHOT_SIZE) { + t_start = sched_clock(); + mutex_lock(&smsm_lock); + for (n = 0; n < SMSM_NUM_ENTRIES; n++) { + state_info = &smsm_states[n]; + + ret = kfifo_out(&smsm_snapshot_fifo, &new_state, + sizeof(new_state)); + if (ret != sizeof(new_state)) { + pr_err("%s: snapshot underflow %d\n", + __func__, ret); + mutex_unlock(&smsm_lock); + return; + } + + state_changes = state_info->last_value ^ new_state; + if (state_changes) { + SMSM_POWER_INFO("SMSM Change %d: %08x->%08x\n", + n, state_info->last_value, + new_state); + list_for_each_entry(cb_info, + &state_info->callbacks, cb_list) { + + if (cb_info->mask & state_changes) + cb_info->notify(cb_info->data, + state_info->last_value, + new_state); + } + state_info->last_value = new_state; + } + } + + ret = kfifo_out(&smsm_snapshot_fifo, &t_snapshot, + sizeof(t_snapshot)); + if (ret != sizeof(t_snapshot)) { + pr_err("%s: snapshot underflow %d\n", + __func__, ret); + mutex_unlock(&smsm_lock); + return; + } + + /* read wakelock flag */ + ret = kfifo_out(&smsm_snapshot_fifo, &use_wakeup_source, + sizeof(use_wakeup_source)); + if (ret != sizeof(use_wakeup_source)) { + pr_err("%s: snapshot underflow %d\n", + __func__, ret); + mutex_unlock(&smsm_lock); + return; + } + mutex_unlock(&smsm_lock); + + if (use_wakeup_source) { + spin_lock_irqsave(&smsm_snapshot_count_lock, flags); + if (smsm_snapshot_count) { + --smsm_snapshot_count; + if (smsm_snapshot_count == 0) { + SMSM_POWER_INFO( + "SMSM snapshot wake unlock\n"); + __pm_relax(&smsm_snapshot_ws); + } + } else { + pr_err("%s: invalid snapshot count\n", + __func__); + } + spin_unlock_irqrestore(&smsm_snapshot_count_lock, + flags); + } + + t_start = t_start - t_snapshot; + nanosec_rem = do_div(t_start, 1000000000U); + SMSM_POWER_INFO( + "SMSM snapshot queue response time %6u.%09lu s\n", + (unsigned)t_start, nanosec_rem); + } +} + + +/** + * Registers callback for SMSM state notifications when the specified + * bits change. + * + * @smsm_entry Processor entry to deregister + * @mask Bits to deregister (if result is 0, callback is removed) + * @notify Notification function to deregister + * @data Opaque data passed in to callback + * + * @returns Status code + * <0 error code + * 0 inserted new entry + * 1 updated mask of existing entry + */ +int smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t, uint32_t), void *data) +{ + struct smsm_state_info *state; + struct smsm_state_cb_info *cb_info; + struct smsm_state_cb_info *cb_found = 0; + uint32_t new_mask = 0; + int ret = 0; + + if (smsm_entry >= SMSM_NUM_ENTRIES) + return -EINVAL; + + mutex_lock(&smsm_lock); + + if (!smsm_states) { + /* smsm not yet initialized */ + ret = -ENODEV; + goto cleanup; + } + + state = &smsm_states[smsm_entry]; + list_for_each_entry(cb_info, + &state->callbacks, cb_list) { + if (!ret && (cb_info->notify == notify) && + (cb_info->data == data)) { + cb_info->mask |= mask; + cb_found = cb_info; + ret = 1; + } + new_mask |= cb_info->mask; + } + + if (!cb_found) { + cb_info = kmalloc(sizeof(struct smsm_state_cb_info), + GFP_ATOMIC); + if (!cb_info) { + ret = -ENOMEM; + goto cleanup; + } + + cb_info->mask = mask; + cb_info->notify = notify; + cb_info->data = data; + INIT_LIST_HEAD(&cb_info->cb_list); + list_add_tail(&cb_info->cb_list, + &state->callbacks); + new_mask |= mask; + } + + /* update interrupt notification mask */ + if (smsm_entry == SMSM_MODEM_STATE) + new_mask |= LEGACY_MODEM_SMSM_MASK; + + if (smsm_info.intr_mask) { + unsigned long flags; + + spin_lock_irqsave(&smem_lock, flags); + new_mask = (new_mask & ~state->intr_mask_clear) + | state->intr_mask_set; + __raw_writel(new_mask, + SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); + wmb(); + spin_unlock_irqrestore(&smem_lock, flags); + } + +cleanup: + mutex_unlock(&smsm_lock); + return ret; +} +EXPORT_SYMBOL(smsm_state_cb_register); + + +/** + * Deregisters for SMSM state notifications for the specified bits. + * + * @smsm_entry Processor entry to deregister + * @mask Bits to deregister (if result is 0, callback is removed) + * @notify Notification function to deregister + * @data Opaque data passed in to callback + * + * @returns Status code + * <0 error code + * 0 not found + * 1 updated mask + * 2 removed callback + */ +int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t, uint32_t), void *data) +{ + struct smsm_state_cb_info *cb_info; + struct smsm_state_cb_info *cb_tmp; + struct smsm_state_info *state; + uint32_t new_mask = 0; + int ret = 0; + + if (smsm_entry >= SMSM_NUM_ENTRIES) + return -EINVAL; + + mutex_lock(&smsm_lock); + + if (!smsm_states) { + /* smsm not yet initialized */ + mutex_unlock(&smsm_lock); + return -ENODEV; + } + + state = &smsm_states[smsm_entry]; + list_for_each_entry_safe(cb_info, cb_tmp, + &state->callbacks, cb_list) { + if (!ret && (cb_info->notify == notify) && + (cb_info->data == data)) { + cb_info->mask &= ~mask; + ret = 1; + if (!cb_info->mask) { + /* no mask bits set, remove callback */ + list_del(&cb_info->cb_list); + kfree(cb_info); + ret = 2; + continue; + } + } + new_mask |= cb_info->mask; + } + + /* update interrupt notification mask */ + if (smsm_entry == SMSM_MODEM_STATE) + new_mask |= LEGACY_MODEM_SMSM_MASK; + + if (smsm_info.intr_mask) { + unsigned long flags; + + spin_lock_irqsave(&smem_lock, flags); + new_mask = (new_mask & ~state->intr_mask_clear) + | state->intr_mask_set; + __raw_writel(new_mask, + SMSM_INTR_MASK_ADDR(smsm_entry, SMSM_APPS)); + wmb(); + spin_unlock_irqrestore(&smem_lock, flags); + } + + mutex_unlock(&smsm_lock); + return ret; +} +EXPORT_SYMBOL(smsm_state_cb_deregister); + +static int restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data); + +static struct restart_notifier_block restart_notifiers[] = { + {SMD_MODEM, "modem", .nb.notifier_call = restart_notifier_cb}, + {SMD_Q6, "lpass", .nb.notifier_call = restart_notifier_cb}, + {SMD_WCNSS, "wcnss", .nb.notifier_call = restart_notifier_cb}, + {SMD_DSPS, "dsps", .nb.notifier_call = restart_notifier_cb}, + {SMD_MODEM, "gss", .nb.notifier_call = restart_notifier_cb}, + {SMD_Q6, "adsp", .nb.notifier_call = restart_notifier_cb}, + {SMD_DSPS, "slpi", .nb.notifier_call = restart_notifier_cb}, +}; + +static int restart_notifier_cb(struct notifier_block *this, + unsigned long code, + void *data) +{ + remote_spinlock_t *remote_spinlock; + + /* + * Some SMD or SMSM clients assume SMD/SMSM SSR handling will be + * done in the AFTER_SHUTDOWN level. If this ever changes, extra + * care should be taken to verify no clients are broken. + */ + if (code == SUBSYS_AFTER_SHUTDOWN) { + struct restart_notifier_block *notifier; + + notifier = container_of(this, + struct restart_notifier_block, nb); + SMD_INFO("%s: ssrestart for processor %d ('%s')\n", + __func__, notifier->processor, + notifier->name); + + remote_spinlock = smem_get_remote_spinlock(); + remote_spin_release(remote_spinlock, notifier->processor); + remote_spin_release_all(notifier->processor); + + smd_channel_reset(notifier->processor); + } + + return NOTIFY_DONE; +} + +/** + * smd_post_init() - SMD post initialization + * @remote_pid: remote pid that has been initialized. Ignored when is_legacy=1 + * + * This function is used by the device tree initialization to complete the SMD + * init sequence. + */ +void smd_post_init(unsigned remote_pid) +{ + smd_channel_probe_now(&remote_info[remote_pid]); +} + +/** + * smsm_post_init() - SMSM post initialization + * @returns: 0 for success, standard Linux error code otherwise + * + * This function is used by the legacy and device tree initialization + * to complete the SMSM init sequence. + */ +int smsm_post_init(void) +{ + int ret; + + ret = smsm_init(); + if (ret) { + pr_err("smsm_init() failed ret = %d\n", ret); + return ret; + } + smsm_irq_handler(0, 0); + + return ret; +} + +/** + * smd_get_intr_config() - Get interrupt configuration structure + * @edge: edge type identifes local and remote processor + * @returns: pointer to interrupt configuration + * + * This function returns the interrupt configuration of remote processor + * based on the edge type. + */ +struct interrupt_config *smd_get_intr_config(uint32_t edge) +{ + if (edge >= ARRAY_SIZE(edge_to_pids)) + return NULL; + return &private_intr_config[edge_to_pids[edge].remote_pid]; +} + +/** + * smd_get_edge_remote_pid() - Get the remote processor ID + * @edge: edge type identifes local and remote processor + * @returns: remote processor ID + * + * This function returns remote processor ID based on edge type. + */ +int smd_edge_to_remote_pid(uint32_t edge) +{ + if (edge >= ARRAY_SIZE(edge_to_pids)) + return -EINVAL; + return edge_to_pids[edge].remote_pid; +} + +/** + * smd_get_edge_local_pid() - Get the local processor ID + * @edge: edge type identifies local and remote processor + * @returns: local processor ID + * + * This function returns local processor ID based on edge type. + */ +int smd_edge_to_local_pid(uint32_t edge) +{ + if (edge >= ARRAY_SIZE(edge_to_pids)) + return -EINVAL; + return edge_to_pids[edge].local_pid; +} + +/** + * smd_proc_set_skip_pil() - Mark if the indicated processor is be loaded by PIL + * @pid: the processor id to mark + * @skip_pil: true if @pid cannot by loaded by PIL + */ +void smd_proc_set_skip_pil(unsigned pid, bool skip_pil) +{ + if (pid >= NUM_SMD_SUBSYSTEMS) { + pr_err("%s: invalid pid:%d\n", __func__, pid); + return; + } + remote_info[pid].skip_pil = skip_pil; +} + +/** + * smd_set_edge_subsys_name() - Set the subsystem name + * @edge: edge type identifies local and remote processor + * @subsys_name: pointer to subsystem name + * + * This function is used to set the subsystem name for given edge type. + */ +void smd_set_edge_subsys_name(uint32_t edge, const char *subsys_name) +{ + if (edge < ARRAY_SIZE(edge_to_pids)) + if (subsys_name) + strlcpy(edge_to_pids[edge].subsys_name, + subsys_name, SMD_MAX_CH_NAME_LEN); + else + strlcpy(edge_to_pids[edge].subsys_name, + "", SMD_MAX_CH_NAME_LEN); + else + pr_err("%s: Invalid edge type[%d]\n", __func__, edge); +} + +/** + * smd_reset_all_edge_subsys_name() - Reset the subsystem name + * + * This function is used to reset the subsystem name of all edges in + * targets where configuration information is available through + * device tree. + */ +void smd_reset_all_edge_subsys_name(void) +{ + int i; + for (i = 0; i < ARRAY_SIZE(edge_to_pids); i++) + strlcpy(edge_to_pids[i].subsys_name, + "", sizeof("")); +} + +/** + * smd_set_edge_initialized() - Set the edge initialized status + * @edge: edge type identifies local and remote processor + * + * This function set the initialized varibale based on edge type. + */ +void smd_set_edge_initialized(uint32_t edge) +{ + if (edge < ARRAY_SIZE(edge_to_pids)) + edge_to_pids[edge].initialized = true; + else + pr_err("%s: Invalid edge type[%d]\n", __func__, edge); +} + +/** + * smd_cfg_smd_intr() - Set the SMD interrupt configuration + * @proc: remote processor ID + * @mask: bit position in IRQ register + * @ptr: IRQ register + * + * This function is called in Legacy init sequence and used to set + * the SMD interrupt configurations for particular processor. + */ +void smd_cfg_smd_intr(uint32_t proc, uint32_t mask, void *ptr) +{ + private_intr_config[proc].smd.out_bit_pos = mask; + private_intr_config[proc].smd.out_base = ptr; + private_intr_config[proc].smd.out_offset = 0; +} + +/* + * smd_cfg_smsm_intr() - Set the SMSM interrupt configuration + * @proc: remote processor ID + * @mask: bit position in IRQ register + * @ptr: IRQ register + * + * This function is called in Legacy init sequence and used to set + * the SMSM interrupt configurations for particular processor. + */ +void smd_cfg_smsm_intr(uint32_t proc, uint32_t mask, void *ptr) +{ + private_intr_config[proc].smsm.out_bit_pos = mask; + private_intr_config[proc].smsm.out_base = ptr; + private_intr_config[proc].smsm.out_offset = 0; +} + +static __init int modem_restart_late_init(void) +{ + int i; + void *handle; + struct restart_notifier_block *nb; + + for (i = 0; i < ARRAY_SIZE(restart_notifiers); i++) { + nb = &restart_notifiers[i]; + handle = subsys_notif_register_notifier(nb->name, &nb->nb); + SMD_DBG("%s: registering notif for '%s', handle=%p\n", + __func__, nb->name, handle); + } + + return 0; +} +late_initcall(modem_restart_late_init); + +int __init msm_smd_init(void) +{ + static bool registered; + int rc; + int i; + + if (registered) + return 0; + + smd_log_ctx = ipc_log_context_create(NUM_LOG_PAGES, "smd", 0); + if (!smd_log_ctx) { + pr_err("%s: unable to create SMD logging context\n", __func__); + msm_smd_debug_mask = 0; + } + + smsm_log_ctx = ipc_log_context_create(NUM_LOG_PAGES, "smsm", 0); + if (!smsm_log_ctx) { + pr_err("%s: unable to create SMSM logging context\n", __func__); + msm_smd_debug_mask = 0; + } + + registered = true; + + for (i = 0; i < NUM_SMD_SUBSYSTEMS; ++i) { + remote_info[i].remote_pid = i; + remote_info[i].free_space = UINT_MAX; + INIT_WORK(&remote_info[i].probe_work, smd_channel_probe_worker); + INIT_LIST_HEAD(&remote_info[i].ch_list); + } + + channel_close_wq = create_singlethread_workqueue("smd_channel_close"); + if (IS_ERR(channel_close_wq)) { + pr_err("%s: create_singlethread_workqueue ENOMEM\n", __func__); + return -ENOMEM; + } + + rc = msm_smd_driver_register(); + if (rc) { + pr_err("%s: msm_smd_driver register failed %d\n", + __func__, rc); + return rc; + } + return 0; +} + +arch_initcall(msm_smd_init); + +MODULE_DESCRIPTION("MSM Shared Memory Core"); +MODULE_AUTHOR("Brian Swetland <swetland@google.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/soc/qcom/smd_debug.c b/drivers/soc/qcom/smd_debug.c new file mode 100644 index 000000000000..62caf88d093b --- /dev/null +++ b/drivers/soc/qcom/smd_debug.c @@ -0,0 +1,429 @@ +/* drivers/soc/qcom/smd_debug.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved. + * Author: Brian Swetland <swetland@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/ctype.h> +#include <linux/jiffies.h> +#include <linux/err.h> + +#include <soc/qcom/smem.h> + +#include "smd_private.h" + +#if defined(CONFIG_DEBUG_FS) + +static char *chstate(unsigned n) +{ + switch (n) { + case SMD_SS_CLOSED: + return "CLOSED"; + case SMD_SS_OPENING: + return "OPENING"; + case SMD_SS_OPENED: + return "OPENED"; + case SMD_SS_FLUSHING: + return "FLUSHING"; + case SMD_SS_CLOSING: + return "CLOSING"; + case SMD_SS_RESET: + return "RESET"; + case SMD_SS_RESET_OPENING: + return "ROPENING"; + default: + return "UNKNOWN"; + } +} + +static void debug_int_stats(struct seq_file *s) +{ + int subsys; + struct interrupt_stat *stats = interrupt_stats; + const char *subsys_name; + + seq_puts(s, + " Subsystem | Interrupt ID | In | Out |\n"); + + for (subsys = 0; subsys < NUM_SMD_SUBSYSTEMS; ++subsys) { + subsys_name = smd_pid_to_subsystem(subsys); + if (!IS_ERR_OR_NULL(subsys_name)) { + seq_printf(s, "%-10s %4s | %9d | %9u | %9u |\n", + smd_pid_to_subsystem(subsys), "smd", + stats->smd_interrupt_id, + stats->smd_in_count, + stats->smd_out_count); + + seq_printf(s, "%-10s %4s | %9d | %9u | %9u |\n", + smd_pid_to_subsystem(subsys), "smsm", + stats->smsm_interrupt_id, + stats->smsm_in_count, + stats->smsm_out_count); + } + ++stats; + } +} + +static void debug_int_stats_reset(struct seq_file *s) +{ + int subsys; + struct interrupt_stat *stats = interrupt_stats; + + seq_puts(s, "Resetting interrupt stats.\n"); + + for (subsys = 0; subsys < NUM_SMD_SUBSYSTEMS; ++subsys) { + stats->smd_in_count = 0; + stats->smd_out_count = 0; + stats->smsm_in_count = 0; + stats->smsm_out_count = 0; + ++stats; + } +} + +/* NNV: revist, it may not be smd version */ +static void debug_read_smd_version(struct seq_file *s) +{ + uint32_t *smd_ver; + uint32_t n, version; + + smd_ver = smem_find(SMEM_VERSION_SMD, 32 * sizeof(uint32_t), + 0, SMEM_ANY_HOST_FLAG); + + if (smd_ver) + for (n = 0; n < 32; n++) { + version = smd_ver[n]; + seq_printf(s, "entry %d: %d.%d\n", n, + version >> 16, + version & 0xffff); + } +} + +/** + * pid_to_str - Convert a numeric processor id value into a human readable + * string value. + * + * @pid: the processor id to convert + * @returns: a string representation of @pid + */ +static char *pid_to_str(int pid) +{ + switch (pid) { + case SMD_APPS: + return "APPS"; + case SMD_MODEM: + return "MDMSW"; + case SMD_Q6: + return "ADSP"; + case SMD_TZ: + return "TZ"; + case SMD_WCNSS: + return "WCNSS"; + case SMD_MODEM_Q6_FW: + return "MDMFW"; + case SMD_RPM: + return "RPM"; + default: + return "???"; + } +} + +/** + * print_half_ch_state - Print the state of half of a SMD channel in a human + * readable format. + * + * @s: the sequential file to print to + * @half_ch: half of a SMD channel that should have its state printed + * @half_ch_funcs: the relevant channel access functions for @half_ch + * @size: size of the fifo in bytes associated with @half_ch + * @proc: the processor id that owns the part of the SMD channel associated with + * @half_ch + * @is_restricted: true if memory access is restricted + */ +static void print_half_ch_state(struct seq_file *s, + void *half_ch, + struct smd_half_channel_access *half_ch_funcs, + unsigned size, + int proc, + bool is_restricted) +{ + seq_printf(s, "%-5s|", pid_to_str(proc)); + + if (!is_restricted) { + seq_printf(s, "%-7s|0x%05X|0x%05X|0x%05X", + chstate(half_ch_funcs->get_state(half_ch)), + size, + half_ch_funcs->get_tail(half_ch), + half_ch_funcs->get_head(half_ch)); + seq_printf(s, "|%c%c%c%c%c%c%c%c|0x%05X", + half_ch_funcs->get_fDSR(half_ch) ? 'D' : 'd', + half_ch_funcs->get_fCTS(half_ch) ? 'C' : 'c', + half_ch_funcs->get_fCD(half_ch) ? 'C' : 'c', + half_ch_funcs->get_fRI(half_ch) ? 'I' : 'i', + half_ch_funcs->get_fHEAD(half_ch) ? 'W' : 'w', + half_ch_funcs->get_fTAIL(half_ch) ? 'R' : 'r', + half_ch_funcs->get_fSTATE(half_ch) ? 'S' : 's', + half_ch_funcs->get_fBLOCKREADINTR(half_ch) ? 'B' : 'b', + (half_ch_funcs->get_head(half_ch) - + half_ch_funcs->get_tail(half_ch)) & (size - 1)); + } else { + seq_puts(s, " Access Restricted"); + } +} + +/** + * smd_xfer_type_to_str - Convert a numeric transfer type value into a human + * readable string value. + * + * @xfer_type: the processor id to convert + * @returns: a string representation of @xfer_type + */ +static char *smd_xfer_type_to_str(uint32_t xfer_type) +{ + if (xfer_type == 1) + return "S"; /* streaming type */ + else if (xfer_type == 2) + return "P"; /* packet type */ + else + return "L"; /* legacy type */ +} + +/** + * print_smd_ch_table - Print the current state of every valid SMD channel in a + * specific SMD channel allocation table to a human + * readable formatted output. + * + * @s: the sequential file to print to + * @tbl: a valid pointer to the channel allocation table to print from + * @num_tbl_entries: total number of entries in the table referenced by @tbl + * @ch_base_id: the SMEM item id corresponding to the array of channel + * structures for the channels found in @tbl + * @fifo_base_id: the SMEM item id corresponding to the array of channel fifos + * for the channels found in @tbl + * @pid: processor id to use for any SMEM operations + * @flags: flags to use for any SMEM operations + */ +static void print_smd_ch_table(struct seq_file *s, + struct smd_alloc_elm *tbl, + unsigned num_tbl_entries, + unsigned ch_base_id, + unsigned fifo_base_id, + unsigned pid, + unsigned flags) +{ + void *half_ch; + unsigned half_ch_size; + uint32_t ch_type; + void *buffer; + unsigned buffer_size; + int n; + bool is_restricted; + +/* + * formatted, human readable channel state output, ie: +ID|CHANNEL NAME |T|PROC |STATE |FIFO SZ|RDPTR |WRPTR |FLAGS |DATAPEN +------------------------------------------------------------------------------- +00|DS |S|APPS |CLOSED |0x02000|0x00000|0x00000|dcCiwrsb|0x00000 + | | |MDMSW|OPENING|0x02000|0x00000|0x00000|dcCiwrsb|0x00000 +------------------------------------------------------------------------------- + */ + + seq_printf(s, "%2s|%-19s|%1s|%-5s|%-7s|%-7s|%-7s|%-7s|%-8s|%-7s\n", + "ID", + "CHANNEL NAME", + "T", + "PROC", + "STATE", + "FIFO SZ", + "RDPTR", + "WRPTR", + "FLAGS", + "DATAPEN"); + seq_puts(s, + "-------------------------------------------------------------------------------\n"); + for (n = 0; n < num_tbl_entries; ++n) { + if (strlen(tbl[n].name) == 0) + continue; + + seq_printf(s, "%2u|%-19s|%s|", tbl[n].cid, tbl[n].name, + smd_xfer_type_to_str(SMD_XFER_TYPE(tbl[n].type))); + ch_type = SMD_CHANNEL_TYPE(tbl[n].type); + + + if (smd_edge_to_remote_pid(ch_type) == SMD_RPM && + smd_edge_to_local_pid(ch_type) != SMD_APPS) + is_restricted = true; + else + is_restricted = false; + + if (is_word_access_ch(ch_type)) + half_ch_size = + sizeof(struct smd_half_channel_word_access); + else + half_ch_size = sizeof(struct smd_half_channel); + + half_ch = smem_find(ch_base_id + n, 2 * half_ch_size, + pid, flags); + buffer = smem_get_entry(fifo_base_id + n, &buffer_size, + pid, flags); + if (half_ch && buffer) + print_half_ch_state(s, + half_ch, + get_half_ch_funcs(ch_type), + buffer_size / 2, + smd_edge_to_local_pid(ch_type), + is_restricted); + + seq_puts(s, "\n"); + seq_printf(s, "%2s|%-19s|%1s|", "", "", ""); + + if (half_ch && buffer) + print_half_ch_state(s, + half_ch + half_ch_size, + get_half_ch_funcs(ch_type), + buffer_size / 2, + smd_edge_to_remote_pid(ch_type), + is_restricted); + + seq_puts(s, "\n"); + seq_puts(s, + "-------------------------------------------------------------------------------\n"); + } +} + +/** + * debug_ch - Print the current state of every valid SMD channel in a human + * readable formatted table. + * + * @s: the sequential file to print to + */ +static void debug_ch(struct seq_file *s) +{ + struct smd_alloc_elm *tbl; + struct smd_alloc_elm *default_pri_tbl; + struct smd_alloc_elm *default_sec_tbl; + unsigned tbl_size; + int i; + + tbl = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size, 0, SMEM_ANY_HOST_FLAG); + default_pri_tbl = tbl; + + if (!tbl) { + seq_puts(s, "Channel allocation table not found\n"); + return; + } + + if (IS_ERR(tbl) && PTR_ERR(tbl) == -EPROBE_DEFER) { + seq_puts(s, "SMEM is not initialized\n"); + return; + } + + seq_puts(s, "Primary allocation table:\n"); + print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl), ID_SMD_CHANNELS, + SMEM_SMD_FIFO_BASE_ID, + 0, + SMEM_ANY_HOST_FLAG); + + tbl = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size, 0, + SMEM_ANY_HOST_FLAG); + default_sec_tbl = tbl; + if (tbl) { + seq_puts(s, "\n\nSecondary allocation table:\n"); + print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl), + SMEM_SMD_BASE_ID_2, + SMEM_SMD_FIFO_BASE_ID_2, + 0, + SMEM_ANY_HOST_FLAG); + } + + for (i = 1; i < NUM_SMD_SUBSYSTEMS; ++i) { + tbl = smem_get_entry(ID_CH_ALLOC_TBL, &tbl_size, i, 0); + if (tbl && tbl != default_pri_tbl) { + seq_puts(s, "\n\n"); + seq_printf(s, "%s <-> %s Primary allocation table:\n", + pid_to_str(SMD_APPS), + pid_to_str(i)); + print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl), + ID_SMD_CHANNELS, + SMEM_SMD_FIFO_BASE_ID, + i, + 0); + } + + tbl = smem_get_entry(SMEM_CHANNEL_ALLOC_TBL_2, &tbl_size, i, 0); + if (tbl && tbl != default_sec_tbl) { + seq_puts(s, "\n\n"); + seq_printf(s, "%s <-> %s Secondary allocation table:\n", + pid_to_str(SMD_APPS), + pid_to_str(i)); + print_smd_ch_table(s, tbl, tbl_size / sizeof(*tbl), + SMEM_SMD_BASE_ID_2, + SMEM_SMD_FIFO_BASE_ID_2, + i, + 0); + } + } +} + +static int debugfs_show(struct seq_file *s, void *data) +{ + void (*show)(struct seq_file *) = s->private; + + show(s); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, debugfs_show, inode->i_private); +} + +static const struct file_operations debug_ops = { + .open = debug_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +static void debug_create(const char *name, umode_t mode, + struct dentry *dent, + void (*show)(struct seq_file *)) +{ + struct dentry *file; + + file = debugfs_create_file(name, mode, dent, show, &debug_ops); + if (!file) + pr_err("%s: unable to create file '%s'\n", __func__, name); +} + +static int __init smd_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("smd", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debug_create("ch", 0444, dent, debug_ch); + debug_create("version", 0444, dent, debug_read_smd_version); + debug_create("int_stats", 0444, dent, debug_int_stats); + debug_create("int_stats_reset", 0444, dent, debug_int_stats_reset); + + return 0; +} + +late_initcall(smd_debugfs_init); +#endif diff --git a/drivers/soc/qcom/smd_init_dt.c b/drivers/soc/qcom/smd_init_dt.c new file mode 100644 index 000000000000..1d722d88e74b --- /dev/null +++ b/drivers/soc/qcom/smd_init_dt.c @@ -0,0 +1,340 @@ +/* drivers/soc/qcom/smd_init_dt.c + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ipc_logging.h> + +#include "smd_private.h" + +#define MODULE_NAME "msm_smd" +#define IPC_LOG(level, x...) do { \ + if (smd_log_ctx) \ + ipc_log_string(smd_log_ctx, x); \ + else \ + printk(level x); \ + } while (0) + +#if defined(CONFIG_MSM_SMD_DEBUG) +#define SMD_DBG(x...) do { \ + if (msm_smd_debug_mask & MSM_SMD_DEBUG) \ + IPC_LOG(KERN_DEBUG, x); \ + } while (0) + +#define SMSM_DBG(x...) do { \ + if (msm_smd_debug_mask & MSM_SMSM_DEBUG) \ + IPC_LOG(KERN_DEBUG, x); \ + } while (0) +#else +#define SMD_DBG(x...) do { } while (0) +#define SMSM_DBG(x...) do { } while (0) +#endif + +static DEFINE_MUTEX(smd_probe_lock); +static int first_probe_done; + +static int msm_smsm_probe(struct platform_device *pdev) +{ + uint32_t edge; + char *key; + int ret; + uint32_t irq_offset; + uint32_t irq_bitmask; + uint32_t irq_line; + struct interrupt_config_item *private_irq; + struct device_node *node; + void *irq_out_base; + resource_size_t irq_out_size; + struct platform_device *parent_pdev; + struct resource *r; + struct interrupt_config *private_intr_config; + uint32_t remote_pid; + + node = pdev->dev.of_node; + + if (!pdev->dev.parent) { + pr_err("%s: missing link to parent device\n", __func__); + return -ENODEV; + } + + parent_pdev = to_platform_device(pdev->dev.parent); + + key = "irq-reg-base"; + r = platform_get_resource_byname(parent_pdev, IORESOURCE_MEM, key); + if (!r) + goto missing_key; + irq_out_size = resource_size(r); + irq_out_base = ioremap_nocache(r->start, irq_out_size); + if (!irq_out_base) { + pr_err("%s: ioremap_nocache() of irq_out_base addr:%pr size:%pr\n", + __func__, &r->start, &irq_out_size); + return -ENOMEM; + } + SMSM_DBG("%s: %s = %p", __func__, key, irq_out_base); + + key = "qcom,smsm-edge"; + ret = of_property_read_u32(node, key, &edge); + if (ret) + goto missing_key; + SMSM_DBG("%s: %s = %d", __func__, key, edge); + + key = "qcom,smsm-irq-offset"; + ret = of_property_read_u32(node, key, &irq_offset); + if (ret) + goto missing_key; + SMSM_DBG("%s: %s = %x", __func__, key, irq_offset); + + key = "qcom,smsm-irq-bitmask"; + ret = of_property_read_u32(node, key, &irq_bitmask); + if (ret) + goto missing_key; + SMSM_DBG("%s: %s = %x", __func__, key, irq_bitmask); + + key = "interrupts"; + irq_line = irq_of_parse_and_map(node, 0); + if (!irq_line) + goto missing_key; + SMSM_DBG("%s: %s = %d", __func__, key, irq_line); + + private_intr_config = smd_get_intr_config(edge); + if (!private_intr_config) { + pr_err("%s: invalid edge\n", __func__); + return -ENODEV; + } + private_irq = &private_intr_config->smsm; + private_irq->out_bit_pos = irq_bitmask; + private_irq->out_offset = irq_offset; + private_irq->out_base = irq_out_base; + private_irq->irq_id = irq_line; + remote_pid = smd_edge_to_remote_pid(edge); + interrupt_stats[remote_pid].smsm_interrupt_id = irq_line; + + ret = request_irq(irq_line, + private_irq->irq_handler, + IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND, + node->name, + NULL); + if (ret < 0) { + pr_err("%s: request_irq() failed on %d\n", __func__, irq_line); + return ret; + } else { + ret = enable_irq_wake(irq_line); + if (ret < 0) + pr_err("%s: enable_irq_wake() failed on %d\n", __func__, + irq_line); + } + + ret = smsm_post_init(); + if (ret) { + pr_err("smd_post_init() failed ret=%d\n", ret); + return ret; + } + + return 0; + +missing_key: + pr_err("%s: missing key: %s", __func__, key); + return -ENODEV; +} + +static int msm_smd_probe(struct platform_device *pdev) +{ + uint32_t edge; + char *key; + int ret; + uint32_t irq_offset; + uint32_t irq_bitmask; + uint32_t irq_line; + const char *subsys_name; + struct interrupt_config_item *private_irq; + struct device_node *node; + void *irq_out_base; + resource_size_t irq_out_size; + struct platform_device *parent_pdev; + struct resource *r; + struct interrupt_config *private_intr_config; + uint32_t remote_pid; + bool skip_pil; + + node = pdev->dev.of_node; + + if (!pdev->dev.parent) { + pr_err("%s: missing link to parent device\n", __func__); + return -ENODEV; + } + + mutex_lock(&smd_probe_lock); + if (!first_probe_done) { + smd_reset_all_edge_subsys_name(); + first_probe_done = 1; + } + mutex_unlock(&smd_probe_lock); + + parent_pdev = to_platform_device(pdev->dev.parent); + + key = "irq-reg-base"; + r = platform_get_resource_byname(parent_pdev, IORESOURCE_MEM, key); + if (!r) + goto missing_key; + irq_out_size = resource_size(r); + irq_out_base = ioremap_nocache(r->start, irq_out_size); + if (!irq_out_base) { + pr_err("%s: ioremap_nocache() of irq_out_base addr:%pr size:%pr\n", + __func__, &r->start, &irq_out_size); + return -ENOMEM; + } + SMD_DBG("%s: %s = %p", __func__, key, irq_out_base); + + key = "qcom,smd-edge"; + ret = of_property_read_u32(node, key, &edge); + if (ret) + goto missing_key; + SMD_DBG("%s: %s = %d", __func__, key, edge); + + key = "qcom,smd-irq-offset"; + ret = of_property_read_u32(node, key, &irq_offset); + if (ret) + goto missing_key; + SMD_DBG("%s: %s = %x", __func__, key, irq_offset); + + key = "qcom,smd-irq-bitmask"; + ret = of_property_read_u32(node, key, &irq_bitmask); + if (ret) + goto missing_key; + SMD_DBG("%s: %s = %x", __func__, key, irq_bitmask); + + key = "interrupts"; + irq_line = irq_of_parse_and_map(node, 0); + if (!irq_line) + goto missing_key; + SMD_DBG("%s: %s = %d", __func__, key, irq_line); + + key = "label"; + subsys_name = of_get_property(node, key, NULL); + SMD_DBG("%s: %s = %s", __func__, key, subsys_name); + /* + * Backwards compatibility. Although label is required, some DTs may + * still list the legacy pil-string. Sanely handle pil-string. + */ + if (!subsys_name) { + pr_warn("msm_smd: Missing required property - label. Using legacy parsing\n"); + key = "qcom,pil-string"; + subsys_name = of_get_property(node, key, NULL); + SMD_DBG("%s: %s = %s", __func__, key, subsys_name); + if (subsys_name) + skip_pil = false; + else + skip_pil = true; + } else { + key = "qcom,not-loadable"; + skip_pil = of_property_read_bool(node, key); + SMD_DBG("%s: %s = %d\n", __func__, key, skip_pil); + } + + private_intr_config = smd_get_intr_config(edge); + if (!private_intr_config) { + pr_err("%s: invalid edge\n", __func__); + return -ENODEV; + } + private_irq = &private_intr_config->smd; + private_irq->out_bit_pos = irq_bitmask; + private_irq->out_offset = irq_offset; + private_irq->out_base = irq_out_base; + private_irq->irq_id = irq_line; + remote_pid = smd_edge_to_remote_pid(edge); + interrupt_stats[remote_pid].smd_interrupt_id = irq_line; + + ret = request_irq(irq_line, + private_irq->irq_handler, + IRQF_TRIGGER_RISING | IRQF_NO_SUSPEND | IRQF_SHARED, + node->name, + &pdev->dev); + if (ret < 0) { + pr_err("%s: request_irq() failed on %d\n", __func__, irq_line); + return ret; + } else { + ret = enable_irq_wake(irq_line); + if (ret < 0) + pr_err("%s: enable_irq_wake() failed on %d\n", __func__, + irq_line); + } + + smd_set_edge_subsys_name(edge, subsys_name); + smd_proc_set_skip_pil(smd_edge_to_remote_pid(edge), skip_pil); + + smd_set_edge_initialized(edge); + smd_post_init(remote_pid); + return 0; + +missing_key: + pr_err("%s: missing key: %s", __func__, key); + return -ENODEV; +} + +static struct of_device_id msm_smd_match_table[] = { + { .compatible = "qcom,smd" }, + {}, +}; + +static struct platform_driver msm_smd_driver = { + .probe = msm_smd_probe, + .driver = { + .name = MODULE_NAME , + .owner = THIS_MODULE, + .of_match_table = msm_smd_match_table, + }, +}; + +static struct of_device_id msm_smsm_match_table[] = { + { .compatible = "qcom,smsm" }, + {}, +}; + +static struct platform_driver msm_smsm_driver = { + .probe = msm_smsm_probe, + .driver = { + .name = "msm_smsm", + .owner = THIS_MODULE, + .of_match_table = msm_smsm_match_table, + }, +}; + +int msm_smd_driver_register(void) +{ + int rc; + + rc = platform_driver_register(&msm_smd_driver); + if (rc) { + pr_err("%s: smd_driver register failed %d\n", + __func__, rc); + return rc; + } + + rc = platform_driver_register(&msm_smsm_driver); + if (rc) { + pr_err("%s: msm_smsm_driver register failed %d\n", + __func__, rc); + return rc; + } + + return 0; +} +EXPORT_SYMBOL(msm_smd_driver_register); + +MODULE_DESCRIPTION("MSM SMD Device Tree Init"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/smd_private.c b/drivers/soc/qcom/smd_private.c new file mode 100644 index 000000000000..a7ef87fc723d --- /dev/null +++ b/drivers/soc/qcom/smd_private.c @@ -0,0 +1,333 @@ +/* Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * 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. + */ + +#include "smd_private.h" + +void set_state(volatile void __iomem *half_channel, unsigned data) +{ + ((struct smd_half_channel __force *)(half_channel))->state = data; +} + +unsigned get_state(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->state; +} + +void set_fDSR(volatile void __iomem *half_channel, unsigned char data) +{ + ((struct smd_half_channel __force *)(half_channel))->fDSR = data; +} + +unsigned get_fDSR(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->fDSR; +} + +void set_fCTS(volatile void __iomem *half_channel, unsigned char data) +{ + ((struct smd_half_channel __force *)(half_channel))->fCTS = data; +} + +unsigned get_fCTS(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->fCTS; +} + +void set_fCD(volatile void __iomem *half_channel, unsigned char data) +{ + ((struct smd_half_channel __force *)(half_channel))->fCD = data; +} + +unsigned get_fCD(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->fCD; +} + +void set_fRI(volatile void __iomem *half_channel, unsigned char data) +{ + ((struct smd_half_channel __force *)(half_channel))->fRI = data; +} + +unsigned get_fRI(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->fRI; +} + +void set_fHEAD(volatile void __iomem *half_channel, unsigned char data) +{ + ((struct smd_half_channel __force *)(half_channel))->fHEAD = data; +} + +unsigned get_fHEAD(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->fHEAD; +} + +void set_fTAIL(volatile void __iomem *half_channel, unsigned char data) +{ + ((struct smd_half_channel __force *)(half_channel))->fTAIL = data; +} + +unsigned get_fTAIL(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->fTAIL; +} + +void set_fSTATE(volatile void __iomem *half_channel, unsigned char data) +{ + ((struct smd_half_channel __force *)(half_channel))->fSTATE = data; +} + +unsigned get_fSTATE(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->fSTATE; +} + +void set_fBLOCKREADINTR(volatile void __iomem *half_channel, unsigned char data) +{ + ((struct smd_half_channel __force *) + (half_channel))->fBLOCKREADINTR = data; +} + +unsigned get_fBLOCKREADINTR(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *) + (half_channel))->fBLOCKREADINTR; +} + +void set_tail(volatile void __iomem *half_channel, unsigned data) +{ + ((struct smd_half_channel __force *)(half_channel))->tail = data; +} + +unsigned get_tail(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->tail; +} + +void set_head(volatile void __iomem *half_channel, unsigned data) +{ + ((struct smd_half_channel __force *)(half_channel))->head = data; +} + +unsigned get_head(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel __force *)(half_channel))->head; +} + +void set_state_word_access(volatile void __iomem *half_channel, unsigned data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->state = data; +} + +unsigned get_state_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->state; +} + +void set_fDSR_word_access(volatile void __iomem *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->fDSR = data; +} + +unsigned get_fDSR_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->fDSR; +} + +void set_fCTS_word_access(volatile void __iomem *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->fCTS = data; +} + +unsigned get_fCTS_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->fCTS; +} + +void set_fCD_word_access(volatile void __iomem *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->fCD = data; +} + +unsigned get_fCD_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->fCD; +} + +void set_fRI_word_access(volatile void __iomem *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->fRI = data; +} + +unsigned get_fRI_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->fRI; +} + +void set_fHEAD_word_access(volatile void __iomem *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->fHEAD = data; +} + +unsigned get_fHEAD_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->fHEAD; +} + +void set_fTAIL_word_access(volatile void __iomem *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->fTAIL = data; +} + +unsigned get_fTAIL_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->fTAIL; +} + +void set_fSTATE_word_access(volatile void __iomem *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->fSTATE = data; +} + +unsigned get_fSTATE_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->fSTATE; +} + +void set_fBLOCKREADINTR_word_access(volatile void __iomem *half_channel, + unsigned char data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->fBLOCKREADINTR = data; +} + +unsigned get_fBLOCKREADINTR_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->fBLOCKREADINTR; +} + +void set_tail_word_access(volatile void __iomem *half_channel, unsigned data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->tail = data; +} + +unsigned get_tail_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->tail; +} + +void set_head_word_access(volatile void __iomem *half_channel, unsigned data) +{ + ((struct smd_half_channel_word_access __force *) + (half_channel))->head = data; +} + +unsigned get_head_word_access(volatile void __iomem *half_channel) +{ + return ((struct smd_half_channel_word_access __force *) + (half_channel))->head; +} + +int is_word_access_ch(unsigned ch_type) +{ + if (ch_type == SMD_APPS_RPM || ch_type == SMD_MODEM_RPM || + ch_type == SMD_QDSP_RPM || ch_type == SMD_WCNSS_RPM || + ch_type == SMD_TZ_RPM) + return 1; + else + return 0; +} + +struct smd_half_channel_access *get_half_ch_funcs(unsigned ch_type) +{ + static struct smd_half_channel_access byte_access = { + .set_state = set_state, + .get_state = get_state, + .set_fDSR = set_fDSR, + .get_fDSR = get_fDSR, + .set_fCTS = set_fCTS, + .get_fCTS = get_fCTS, + .set_fCD = set_fCD, + .get_fCD = get_fCD, + .set_fRI = set_fRI, + .get_fRI = get_fRI, + .set_fHEAD = set_fHEAD, + .get_fHEAD = get_fHEAD, + .set_fTAIL = set_fTAIL, + .get_fTAIL = get_fTAIL, + .set_fSTATE = set_fSTATE, + .get_fSTATE = get_fSTATE, + .set_fBLOCKREADINTR = set_fBLOCKREADINTR, + .get_fBLOCKREADINTR = get_fBLOCKREADINTR, + .set_tail = set_tail, + .get_tail = get_tail, + .set_head = set_head, + .get_head = get_head, + }; + static struct smd_half_channel_access word_access = { + .set_state = set_state_word_access, + .get_state = get_state_word_access, + .set_fDSR = set_fDSR_word_access, + .get_fDSR = get_fDSR_word_access, + .set_fCTS = set_fCTS_word_access, + .get_fCTS = get_fCTS_word_access, + .set_fCD = set_fCD_word_access, + .get_fCD = get_fCD_word_access, + .set_fRI = set_fRI_word_access, + .get_fRI = get_fRI_word_access, + .set_fHEAD = set_fHEAD_word_access, + .get_fHEAD = get_fHEAD_word_access, + .set_fTAIL = set_fTAIL_word_access, + .get_fTAIL = get_fTAIL_word_access, + .set_fSTATE = set_fSTATE_word_access, + .get_fSTATE = get_fSTATE_word_access, + .set_fBLOCKREADINTR = set_fBLOCKREADINTR_word_access, + .get_fBLOCKREADINTR = get_fBLOCKREADINTR_word_access, + .set_tail = set_tail_word_access, + .get_tail = get_tail_word_access, + .set_head = set_head_word_access, + .get_head = get_head_word_access, + }; + + if (is_word_access_ch(ch_type)) + return &word_access; + else + return &byte_access; +} + diff --git a/drivers/soc/qcom/smd_private.h b/drivers/soc/qcom/smd_private.h new file mode 100644 index 000000000000..4beb6b8c125d --- /dev/null +++ b/drivers/soc/qcom/smd_private.h @@ -0,0 +1,243 @@ +/* drivers/soc/qcom/smd_private.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2007-2014, The Linux Foundation. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#ifndef _ARCH_ARM_MACH_MSM_MSM_SMD_PRIVATE_H_ +#define _ARCH_ARM_MACH_MSM_MSM_SMD_PRIVATE_H_ + +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/remote_spinlock.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> + +#include <soc/qcom/smd.h> +#include <soc/qcom/smsm.h> + +#define VERSION_QDSP6 4 +#define VERSION_APPS_SBL 6 +#define VERSION_MODEM_SBL 7 +#define VERSION_APPS 8 +#define VERSION_MODEM 9 +#define VERSION_DSPS 10 + +#define ID_SMD_CHANNELS SMEM_SMD_BASE_ID +#define ID_SHARED_STATE SMEM_SMSM_SHARED_STATE +#define ID_CH_ALLOC_TBL SMEM_CHANNEL_ALLOC_TBL + +#define SMD_SS_CLOSED 0x00000000 +#define SMD_SS_OPENING 0x00000001 +#define SMD_SS_OPENED 0x00000002 +#define SMD_SS_FLUSHING 0x00000003 +#define SMD_SS_CLOSING 0x00000004 +#define SMD_SS_RESET 0x00000005 +#define SMD_SS_RESET_OPENING 0x00000006 + +#define SMD_HEADER_SIZE 20 + +/* 'type' field of smd_alloc_elm structure + * has the following breakup + * bits 0-7 -> channel type + * bits 8-11 -> xfer type + * bits 12-31 -> reserved + */ +struct smd_alloc_elm { + char name[20]; + uint32_t cid; + uint32_t type; + uint32_t ref_count; +}; + +#define SMD_CHANNEL_TYPE(x) ((x) & 0x000000FF) +#define SMD_XFER_TYPE(x) (((x) & 0x00000F00) >> 8) + +struct smd_half_channel { + unsigned state; + unsigned char fDSR; + unsigned char fCTS; + unsigned char fCD; + unsigned char fRI; + unsigned char fHEAD; + unsigned char fTAIL; + unsigned char fSTATE; + unsigned char fBLOCKREADINTR; + unsigned tail; + unsigned head; +}; + +struct smd_half_channel_word_access { + unsigned state; + unsigned fDSR; + unsigned fCTS; + unsigned fCD; + unsigned fRI; + unsigned fHEAD; + unsigned fTAIL; + unsigned fSTATE; + unsigned fBLOCKREADINTR; + unsigned tail; + unsigned head; +}; + +struct smd_half_channel_access { + void (*set_state)(volatile void __iomem *half_channel, unsigned data); + unsigned (*get_state)(volatile void __iomem *half_channel); + void (*set_fDSR)(volatile void __iomem *half_channel, + unsigned char data); + unsigned (*get_fDSR)(volatile void __iomem *half_channel); + void (*set_fCTS)(volatile void __iomem *half_channel, + unsigned char data); + unsigned (*get_fCTS)(volatile void __iomem *half_channel); + void (*set_fCD)(volatile void __iomem *half_channel, + unsigned char data); + unsigned (*get_fCD)(volatile void __iomem *half_channel); + void (*set_fRI)(volatile void __iomem *half_channel, + unsigned char data); + unsigned (*get_fRI)(volatile void __iomem *half_channel); + void (*set_fHEAD)(volatile void __iomem *half_channel, + unsigned char data); + unsigned (*get_fHEAD)(volatile void __iomem *half_channel); + void (*set_fTAIL)(volatile void __iomem *half_channel, + unsigned char data); + unsigned (*get_fTAIL)(volatile void __iomem *half_channel); + void (*set_fSTATE)(volatile void __iomem *half_channel, + unsigned char data); + unsigned (*get_fSTATE)(volatile void __iomem *half_channel); + void (*set_fBLOCKREADINTR)(volatile void __iomem *half_channel, + unsigned char data); + unsigned (*get_fBLOCKREADINTR)(volatile void __iomem *half_channel); + void (*set_tail)(volatile void __iomem *half_channel, unsigned data); + unsigned (*get_tail)(volatile void __iomem *half_channel); + void (*set_head)(volatile void __iomem *half_channel, unsigned data); + unsigned (*get_head)(volatile void __iomem *half_channel); +}; + +int is_word_access_ch(unsigned ch_type); + +struct smd_half_channel_access *get_half_ch_funcs(unsigned ch_type); + +struct smd_channel { + volatile void __iomem *send; /* some variant of smd_half_channel */ + volatile void __iomem *recv; /* some variant of smd_half_channel */ + unsigned char *send_data; + unsigned char *recv_data; + unsigned fifo_size; + struct list_head ch_list; + + unsigned current_packet; + unsigned n; + void *priv; + void (*notify)(void *priv, unsigned flags); + + int (*read)(smd_channel_t *ch, void *data, int len); + int (*write)(smd_channel_t *ch, const void *data, int len, + bool int_ntfy); + int (*read_avail)(smd_channel_t *ch); + int (*write_avail)(smd_channel_t *ch); + int (*read_from_cb)(smd_channel_t *ch, void *data, int len); + + void (*update_state)(smd_channel_t *ch); + unsigned last_state; + void (*notify_other_cpu)(smd_channel_t *ch); + void * (*read_from_fifo)(void *dest, const void *src, size_t num_bytes); + void * (*write_to_fifo)(void *dest, const void *src, size_t num_bytes); + + char name[20]; + struct platform_device pdev; + unsigned type; + + int pending_pkt_sz; + + char is_pkt_ch; + + /* + * private internal functions to access *send and *recv. + * never to be exported outside of smd + */ + struct smd_half_channel_access *half_ch; +}; + +extern spinlock_t smem_lock; + +struct interrupt_stat { + uint32_t smd_in_count; + uint32_t smd_out_count; + uint32_t smd_interrupt_id; + + uint32_t smsm_in_count; + uint32_t smsm_out_count; + uint32_t smsm_interrupt_id; +}; +extern struct interrupt_stat interrupt_stats[NUM_SMD_SUBSYSTEMS]; + +struct interrupt_config_item { + /* must be initialized */ + irqreturn_t (*irq_handler)(int req, void *data); + /* outgoing interrupt config (set from platform data) */ + uint32_t out_bit_pos; + void __iomem *out_base; + uint32_t out_offset; + int irq_id; +}; + +enum { + MSM_SMD_DEBUG = 1U << 0, + MSM_SMSM_DEBUG = 1U << 1, + MSM_SMD_INFO = 1U << 2, + MSM_SMSM_INFO = 1U << 3, + MSM_SMD_POWER_INFO = 1U << 4, + MSM_SMSM_POWER_INFO = 1U << 5, +}; + +struct interrupt_config { + struct interrupt_config_item smd; + struct interrupt_config_item smsm; +}; + +struct edge_to_pid { + uint32_t local_pid; + uint32_t remote_pid; + char subsys_name[SMD_MAX_CH_NAME_LEN]; + bool initialized; +}; + +extern void *smd_log_ctx; +extern int msm_smd_debug_mask; + +extern irqreturn_t smd_modem_irq_handler(int irq, void *data); +extern irqreturn_t smsm_modem_irq_handler(int irq, void *data); +extern irqreturn_t smd_dsp_irq_handler(int irq, void *data); +extern irqreturn_t smsm_dsp_irq_handler(int irq, void *data); +extern irqreturn_t smd_dsps_irq_handler(int irq, void *data); +extern irqreturn_t smsm_dsps_irq_handler(int irq, void *data); +extern irqreturn_t smd_wcnss_irq_handler(int irq, void *data); +extern irqreturn_t smsm_wcnss_irq_handler(int irq, void *data); +extern irqreturn_t smd_rpm_irq_handler(int irq, void *data); +extern irqreturn_t smd_modemfw_irq_handler(int irq, void *data); + +extern int msm_smd_driver_register(void); +extern void smd_post_init(unsigned remote_pid); +extern int smsm_post_init(void); + +extern struct interrupt_config *smd_get_intr_config(uint32_t edge); +extern int smd_edge_to_remote_pid(uint32_t edge); +extern int smd_edge_to_local_pid(uint32_t edge); +extern void smd_set_edge_subsys_name(uint32_t edge, const char *subsys_name); +extern void smd_reset_all_edge_subsys_name(void); +extern void smd_proc_set_skip_pil(unsigned pid, bool skip_pil); +extern void smd_set_edge_initialized(uint32_t edge); +extern void smd_cfg_smd_intr(uint32_t proc, uint32_t mask, void *ptr); +extern void smd_cfg_smsm_intr(uint32_t proc, uint32_t mask, void *ptr); +#endif diff --git a/drivers/soc/qcom/smsm_debug.c b/drivers/soc/qcom/smsm_debug.c new file mode 100644 index 000000000000..b9e42e46c888 --- /dev/null +++ b/drivers/soc/qcom/smsm_debug.c @@ -0,0 +1,330 @@ +/* drivers/soc/qcom/smsm_debug.c + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved. + * Author: Brian Swetland <swetland@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/debugfs.h> +#include <linux/list.h> +#include <linux/ctype.h> +#include <linux/jiffies.h> + +#include <soc/qcom/smem.h> +#include <soc/qcom/smsm.h> + +#if defined(CONFIG_DEBUG_FS) + + +static void debug_read_smsm_state(struct seq_file *s) +{ + uint32_t *smsm; + int n; + + smsm = smem_find(SMEM_SMSM_SHARED_STATE, + SMSM_NUM_ENTRIES * sizeof(uint32_t), + 0, + SMEM_ANY_HOST_FLAG); + + if (smsm) + for (n = 0; n < SMSM_NUM_ENTRIES; n++) + seq_printf(s, "entry %d: 0x%08x\n", n, smsm[n]); +} + +struct SMSM_CB_DATA { + int cb_count; + void *data; + uint32_t old_state; + uint32_t new_state; +}; +static struct SMSM_CB_DATA smsm_cb_data; +static struct completion smsm_cb_completion; + +static void smsm_state_cb(void *data, uint32_t old_state, uint32_t new_state) +{ + smsm_cb_data.cb_count++; + smsm_cb_data.old_state = old_state; + smsm_cb_data.new_state = new_state; + smsm_cb_data.data = data; + complete_all(&smsm_cb_completion); +} + +#define UT_EQ_INT(a, b) \ + { \ + if ((a) != (b)) { \ + seq_printf(s, "%s:%d " #a "(%d) != " #b "(%d)\n", \ + __func__, __LINE__, \ + a, b); \ + break; \ + } \ + } + +#define UT_GT_INT(a, b) \ + { \ + if ((a) <= (b)) { \ + seq_printf(s, "%s:%d " #a "(%d) > " #b "(%d)\n", \ + __func__, __LINE__, \ + a, b); \ + break; \ + } \ + } + +#define SMSM_CB_TEST_INIT() \ + do { \ + smsm_cb_data.cb_count = 0; \ + smsm_cb_data.old_state = 0; \ + smsm_cb_data.new_state = 0; \ + smsm_cb_data.data = 0; \ + } while (0) + + +static void debug_test_smsm(struct seq_file *s) +{ + int test_num = 0; + int ret; + + /* Test case 1 - Register new callback for notification */ + do { + test_num++; + SMSM_CB_TEST_INIT(); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 0); + + /* de-assert SMSM_SMD_INIT to trigger state update */ + UT_EQ_INT(smsm_cb_data.cb_count, 0); + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + + UT_EQ_INT(smsm_cb_data.cb_count, 1); + UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, SMSM_SMDINIT); + UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, 0x0); + UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x1234); + + /* re-assert SMSM_SMD_INIT to trigger state update */ + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + UT_EQ_INT(smsm_cb_data.old_state & SMSM_SMDINIT, 0x0); + UT_EQ_INT(smsm_cb_data.new_state & SMSM_SMDINIT, SMSM_SMDINIT); + + /* deregister callback */ + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 2); + + /* make sure state change doesn't cause any more callbacks */ + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + + seq_printf(s, "Test %d - PASS\n", test_num); + } while (0); + + /* Test case 2 - Update already registered callback */ + do { + test_num++; + SMSM_CB_TEST_INIT(); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 0); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_INIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 1); + + /* verify both callback bits work */ + reinit_completion(&smsm_cb_completion); + UT_EQ_INT(smsm_cb_data.cb_count, 0); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 1); + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 3); + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 4); + + /* deregister 1st callback */ + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 1); + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 4); + + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 5); + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 6); + + /* deregister 2nd callback */ + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, SMSM_INIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 2); + + /* make sure state change doesn't cause any more callbacks */ + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + UT_EQ_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 6); + + seq_printf(s, "Test %d - PASS\n", test_num); + } while (0); + + /* Test case 3 - Two callback registrations with different data */ + do { + test_num++; + SMSM_CB_TEST_INIT(); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 0); + ret = smsm_state_cb_register(SMSM_APPS_STATE, SMSM_INIT, + smsm_state_cb, (void *)0x3456); + UT_EQ_INT(ret, 0); + + /* verify both callbacks work */ + reinit_completion(&smsm_cb_completion); + UT_EQ_INT(smsm_cb_data.cb_count, 0); + smsm_change_state(SMSM_APPS_STATE, SMSM_SMDINIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 1); + UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x1234); + + reinit_completion(&smsm_cb_completion); + smsm_change_state(SMSM_APPS_STATE, SMSM_INIT, 0x0); + UT_GT_INT((int)wait_for_completion_timeout(&smsm_cb_completion, + msecs_to_jiffies(20)), 0); + UT_EQ_INT(smsm_cb_data.cb_count, 2); + UT_EQ_INT((int)(uintptr_t)smsm_cb_data.data, 0x3456); + + /* cleanup and unregister + * degregister in reverse to verify data field is + * being used + */ + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_SMDINIT); + smsm_change_state(SMSM_APPS_STATE, 0x0, SMSM_INIT); + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, + SMSM_INIT, + smsm_state_cb, (void *)0x3456); + UT_EQ_INT(ret, 2); + ret = smsm_state_cb_deregister(SMSM_APPS_STATE, + SMSM_SMDINIT, + smsm_state_cb, (void *)0x1234); + UT_EQ_INT(ret, 2); + + seq_printf(s, "Test %d - PASS\n", test_num); + } while (0); +} + +static void debug_read_intr_mask(struct seq_file *s) +{ + uint32_t *smsm; + int m, n; + + smsm = smem_find(SMEM_SMSM_CPU_INTR_MASK, + SMSM_NUM_ENTRIES * SMSM_NUM_HOSTS * sizeof(uint32_t), + 0, + SMEM_ANY_HOST_FLAG); + + if (smsm) + for (m = 0; m < SMSM_NUM_ENTRIES; m++) { + seq_printf(s, "entry %d:", m); + for (n = 0; n < SMSM_NUM_HOSTS; n++) + seq_printf(s, " host %d: 0x%08x", + n, smsm[m * SMSM_NUM_HOSTS + n]); + seq_puts(s, "\n"); + } +} + +static int debugfs_show(struct seq_file *s, void *data) +{ + void (*show)(struct seq_file *) = s->private; + + show(s); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, debugfs_show, inode->i_private); +} + +static const struct file_operations debug_ops = { + .open = debug_open, + .release = single_release, + .read = seq_read, + .llseek = seq_lseek, +}; + +static void debug_create(const char *name, umode_t mode, + struct dentry *dent, + void (*show)(struct seq_file *)) +{ + struct dentry *file; + + file = debugfs_create_file(name, mode, dent, show, &debug_ops); + if (!file) + pr_err("%s: unable to create file '%s'\n", __func__, name); +} + +static int __init smsm_debugfs_init(void) +{ + struct dentry *dent; + + dent = debugfs_create_dir("smsm", 0); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + debug_create("state", 0444, dent, debug_read_smsm_state); + debug_create("intr_mask", 0444, dent, debug_read_intr_mask); + debug_create("smsm_test", 0444, dent, debug_test_smsm); + + init_completion(&smsm_cb_completion); + + return 0; +} + +late_initcall(smsm_debugfs_init); +#endif diff --git a/include/soc/qcom/smd.h b/include/soc/qcom/smd.h new file mode 100644 index 000000000000..50c32d191055 --- /dev/null +++ b/include/soc/qcom/smd.h @@ -0,0 +1,381 @@ +/* include/soc/qcom/smd.h + * + * Copyright (C) 2007 Google, Inc. + * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved. + * Author: Brian Swetland <swetland@google.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __ASM_ARCH_MSM_SMD_H +#define __ASM_ARCH_MSM_SMD_H + +#include <linux/io.h> + +#include <soc/qcom/smem.h> + +typedef struct smd_channel smd_channel_t; +struct cpumask; + +#define SMD_MAX_CH_NAME_LEN 20 /* includes null char at end */ + +#define SMD_EVENT_DATA 1 +#define SMD_EVENT_OPEN 2 +#define SMD_EVENT_CLOSE 3 +#define SMD_EVENT_STATUS 4 +#define SMD_EVENT_REOPEN_READY 5 + +/* + * SMD Processor ID's. + * + * For all processors that have both SMSM and SMD clients, + * the SMSM Processor ID and the SMD Processor ID will + * be the same. In cases where a processor only supports + * SMD, the entry will only exist in this enum. + */ +enum { + SMD_APPS = SMEM_APPS, + SMD_MODEM = SMEM_MODEM, + SMD_Q6 = SMEM_Q6, + SMD_DSPS = SMEM_DSPS, + SMD_TZ = SMEM_DSPS, + SMD_WCNSS = SMEM_WCNSS, + SMD_MODEM_Q6_FW = SMEM_MODEM_Q6_FW, + SMD_RPM = SMEM_RPM, + NUM_SMD_SUBSYSTEMS, +}; + +enum { + SMD_APPS_MODEM = 0, + SMD_APPS_QDSP, + SMD_MODEM_QDSP, + SMD_APPS_DSPS, + SMD_MODEM_DSPS, + SMD_QDSP_DSPS, + SMD_APPS_WCNSS, + SMD_MODEM_WCNSS, + SMD_QDSP_WCNSS, + SMD_DSPS_WCNSS, + SMD_APPS_Q6FW, + SMD_MODEM_Q6FW, + SMD_QDSP_Q6FW, + SMD_DSPS_Q6FW, + SMD_WCNSS_Q6FW, + SMD_APPS_RPM, + SMD_MODEM_RPM, + SMD_QDSP_RPM, + SMD_WCNSS_RPM, + SMD_TZ_RPM, + SMD_NUM_TYPE, + +}; + +#ifdef CONFIG_MSM_SMD +int smd_close(smd_channel_t *ch); + +/* passing a null pointer for data reads and discards */ +int smd_read(smd_channel_t *ch, void *data, int len); +int smd_read_from_cb(smd_channel_t *ch, void *data, int len); + +/* Write to stream channels may do a partial write and return +** the length actually written. +** Write to packet channels will never do a partial write -- +** it will return the requested length written or an error. +*/ +int smd_write(smd_channel_t *ch, const void *data, int len); + +int smd_write_avail(smd_channel_t *ch); +int smd_read_avail(smd_channel_t *ch); + +/* Returns the total size of the current packet being read. +** Returns 0 if no packets available or a stream channel. +*/ +int smd_cur_packet_size(smd_channel_t *ch); + +/* these are used to get and set the IF sigs of a channel. + * DTR and RTS can be set; DSR, CTS, CD and RI can be read. + */ +int smd_tiocmget(smd_channel_t *ch); +int smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear); +int +smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear); +int smd_named_open_on_edge(const char *name, uint32_t edge, smd_channel_t **_ch, + void *priv, void (*notify)(void *, unsigned)); + +/* Tells the other end of the smd channel that this end wants to recieve + * interrupts when the written data is read. Read interrupts should only + * enabled when there is no space left in the buffer to write to, thus the + * interrupt acts as notification that space may be avaliable. If the + * other side does not support enabling/disabling interrupts on demand, + * then this function has no effect if called. + */ +void smd_enable_read_intr(smd_channel_t *ch); + +/* Tells the other end of the smd channel that this end does not want + * interrupts when written data is read. The interrupts should be + * disabled by default. If the other side does not support enabling/ + * disabling interrupts on demand, then this function has no effect if + * called. + */ +void smd_disable_read_intr(smd_channel_t *ch); + +/** + * Enable/disable receive interrupts for the remote processor used by a + * particular channel. + * @ch: open channel handle to use for the edge + * @mask: 1 = mask interrupts; 0 = unmask interrupts + * @cpumask cpumask for the next cpu scheduled to be woken up + * @returns: 0 for success; < 0 for failure + * + * Note that this enables/disables all interrupts from the remote subsystem for + * all channels. As such, it should be used with care and only for specific + * use cases such as power-collapse sequencing. + */ +int smd_mask_receive_interrupt(smd_channel_t *ch, bool mask, + const struct cpumask *cpumask); + +/* Starts a packet transaction. The size of the packet may exceed the total + * size of the smd ring buffer. + * + * @ch: channel to write the packet to + * @len: total length of the packet + * + * Returns: + * 0 - success + * -ENODEV - invalid smd channel + * -EACCES - non-packet channel specified + * -EINVAL - invalid length + * -EBUSY - transaction already in progress + * -EAGAIN - no enough memory in ring buffer to start transaction + * -EPERM - unable to sucessfully start transaction due to write error + */ +int smd_write_start(smd_channel_t *ch, int len); + +/* Writes a segment of the packet for a packet transaction. + * + * @ch: channel to write packet to + * @data: buffer of data to write + * @len: length of data buffer + * + * Returns: + * number of bytes written + * -ENODEV - invalid smd channel + * -EINVAL - invalid length + * -ENOEXEC - transaction not started + */ +int smd_write_segment(smd_channel_t *ch, const void *data, int len); + +/* Completes a packet transaction. Do not call from interrupt context. + * + * @ch: channel to complete transaction on + * + * Returns: + * 0 - success + * -ENODEV - invalid smd channel + * -E2BIG - some ammount of packet is not yet written + */ +int smd_write_end(smd_channel_t *ch); + +/** + * smd_write_segment_avail() - available write space for packet transactions + * @ch: channel to write packet to + * @returns: number of bytes available to write to, or -ENODEV for invalid ch + * + * This is a version of smd_write_avail() intended for use with packet + * transactions. This version correctly accounts for any internal reserved + * space at all stages of the transaction. + */ +int smd_write_segment_avail(smd_channel_t *ch); + +/* + * Returns a pointer to the subsystem name or NULL if no + * subsystem name is available. + * + * @type - Edge definition + */ +const char *smd_edge_to_subsystem(uint32_t type); + +/* + * Returns a pointer to the subsystem name given the + * remote processor ID. + * + * @pid Remote processor ID + * @returns Pointer to subsystem name or NULL if not found + */ +const char *smd_pid_to_subsystem(uint32_t pid); + +/* + * Checks to see if a new packet has arrived on the channel. Only to be + * called with interrupts disabled. + * + * @ch: channel to check if a packet has arrived + * + * Returns: + * 0 - packet not available + * 1 - packet available + * -EINVAL - NULL parameter or non-packet based channel provided + */ +int smd_is_pkt_avail(smd_channel_t *ch); + +/* + * SMD initialization function that registers for a SMD platform driver. + * + * returns success on successful driver registration. + */ +int __init msm_smd_init(void); + +/** + * smd_remote_ss_to_edge() - return edge type from remote ss type + * @name: remote subsystem name + * + * Returns the edge type connected between the local subsystem(APPS) + * and remote subsystem @name. + */ +int smd_remote_ss_to_edge(const char *name); + +/** + * smd_edge_to_pil_str - Returns the PIL string used to load the remote side of + * the indicated edge. + * + * @type - Edge definition + * @returns - The PIL string to load the remove side of @type or NULL if the + * PIL string does not exist. + */ +const char *smd_edge_to_pil_str(uint32_t type); + +#else + +static inline int smd_close(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_read(smd_channel_t *ch, void *data, int len) +{ + return -ENODEV; +} + +static inline int smd_read_from_cb(smd_channel_t *ch, void *data, int len) +{ + return -ENODEV; +} + +static inline int smd_write(smd_channel_t *ch, const void *data, int len) +{ + return -ENODEV; +} + +static inline int smd_write_avail(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_read_avail(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_cur_packet_size(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_tiocmget(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int +smd_tiocmset(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + return -ENODEV; +} + +static inline int +smd_tiocmset_from_cb(smd_channel_t *ch, unsigned int set, unsigned int clear) +{ + return -ENODEV; +} + +static inline int +smd_named_open_on_edge(const char *name, uint32_t edge, smd_channel_t **_ch, + void *priv, void (*notify)(void *, unsigned)) +{ + return -ENODEV; +} + +static inline void smd_enable_read_intr(smd_channel_t *ch) +{ +} + +static inline void smd_disable_read_intr(smd_channel_t *ch) +{ +} + +static inline int smd_mask_receive_interrupt(smd_channel_t *ch, bool mask, + const struct cpumask *cpumask) +{ + return -ENODEV; +} + +static inline int smd_write_start(smd_channel_t *ch, int len) +{ + return -ENODEV; +} + +static inline int +smd_write_segment(smd_channel_t *ch, const void *data, int len) +{ + return -ENODEV; +} + +static inline int smd_write_end(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int smd_write_segment_avail(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline const char *smd_edge_to_subsystem(uint32_t type) +{ + return NULL; +} + +static inline const char *smd_pid_to_subsystem(uint32_t pid) +{ + return NULL; +} + +static inline int smd_is_pkt_avail(smd_channel_t *ch) +{ + return -ENODEV; +} + +static inline int __init msm_smd_init(void) +{ + return 0; +} + +static inline int smd_remote_ss_to_edge(const char *name) +{ + return -EINVAL; +} + +static inline const char *smd_edge_to_pil_str(uint32_t type) +{ + return NULL; +} +#endif + +#endif diff --git a/include/soc/qcom/smsm.h b/include/soc/qcom/smsm.h new file mode 100644 index 000000000000..96ca0c86912c --- /dev/null +++ b/include/soc/qcom/smsm.h @@ -0,0 +1,147 @@ +/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved. + * + * 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. + */ + +#ifndef _ARCH_ARM_MACH_MSM_SMSM_H_ +#define _ARCH_ARM_MACH_MSM_SMSM_H_ + +#include <soc/qcom/smem.h> + +enum { + SMSM_APPS_STATE, + SMSM_MODEM_STATE, + SMSM_Q6_STATE, + SMSM_APPS_DEM, + SMSM_WCNSS_STATE = SMSM_APPS_DEM, + SMSM_MODEM_DEM, + SMSM_DSPS_STATE = SMSM_MODEM_DEM, + SMSM_Q6_DEM, + SMSM_POWER_MASTER_DEM, + SMSM_TIME_MASTER_DEM, +}; +extern uint32_t SMSM_NUM_ENTRIES; + +/* + * Ordered by when processors adopted the SMSM protocol. May not be 1-to-1 + * with SMEM PIDs, despite initial expectations. + */ +enum { + SMSM_APPS = SMEM_APPS, + SMSM_MODEM = SMEM_MODEM, + SMSM_Q6 = SMEM_Q6, + SMSM_WCNSS, + SMSM_DSPS, +}; +extern uint32_t SMSM_NUM_HOSTS; + +#define SMSM_INIT 0x00000001 +#define SMSM_SMDINIT 0x00000008 +#define SMSM_RPCINIT 0x00000020 +#define SMSM_RESET 0x00000040 +#define SMSM_TIMEWAIT 0x00000400 +#define SMSM_TIMEINIT 0x00000800 +#define SMSM_PROC_AWAKE 0x00001000 +#define SMSM_SMD_LOOPBACK 0x00800000 + +#define SMSM_USB_PLUG_UNPLUG 0x00002000 + +#define SMSM_A2_POWER_CONTROL 0x00000002 +#define SMSM_A2_POWER_CONTROL_ACK 0x00000800 + +#ifdef CONFIG_MSM_SMD +int smsm_change_state(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask); + +/* + * Changes the global interrupt mask. The set and clear masks are re-applied + * every time the global interrupt mask is updated for callback registration + * and de-registration. + * + * The clear mask is applied first, so if a bit is set to 1 in both the clear + * mask and the set mask, the result will be that the interrupt is set. + * + * @smsm_entry SMSM entry to change + * @clear_mask 1 = clear bit, 0 = no-op + * @set_mask 1 = set bit, 0 = no-op + * + * @returns 0 for success, < 0 for error + */ +int smsm_change_intr_mask(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask); +int smsm_get_intr_mask(uint32_t smsm_entry, uint32_t *intr_mask); +uint32_t smsm_get_state(uint32_t smsm_entry); +int smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t old_state, uint32_t new_state), + void *data); +int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t, uint32_t), void *data); + +#else +static inline int smsm_change_state(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask) +{ + return -ENODEV; +} + +/* + * Changes the global interrupt mask. The set and clear masks are re-applied + * every time the global interrupt mask is updated for callback registration + * and de-registration. + * + * The clear mask is applied first, so if a bit is set to 1 in both the clear + * mask and the set mask, the result will be that the interrupt is set. + * + * @smsm_entry SMSM entry to change + * @clear_mask 1 = clear bit, 0 = no-op + * @set_mask 1 = set bit, 0 = no-op + * + * @returns 0 for success, < 0 for error + */ +static inline int smsm_change_intr_mask(uint32_t smsm_entry, + uint32_t clear_mask, uint32_t set_mask) +{ + return -ENODEV; +} + +static inline int smsm_get_intr_mask(uint32_t smsm_entry, uint32_t *intr_mask) +{ + return -ENODEV; +} +static inline uint32_t smsm_get_state(uint32_t smsm_entry) +{ + return 0; +} +static inline int smsm_state_cb_register(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t old_state, uint32_t new_state), + void *data) +{ + return -ENODEV; +} +static inline int smsm_state_cb_deregister(uint32_t smsm_entry, uint32_t mask, + void (*notify)(void *, uint32_t, uint32_t), void *data) +{ + return -ENODEV; +} +static inline void smsm_reset_modem(unsigned mode) +{ +} +static inline void smsm_reset_modem_cont(void) +{ +} +static inline void smd_sleep_exit(void) +{ +} +static inline int smsm_check_for_modem_crash(void) +{ + return -ENODEV; +} +#endif +#endif |
