diff options
author | Olof Johansson <olof@lixom.net> | 2015-12-22 12:10:37 -0800 |
---|---|---|
committer | Olof Johansson <olof@lixom.net> | 2015-12-22 12:10:37 -0800 |
commit | 47ec7e0b7567f10da2975e05fa446be204dc2fcb (patch) | |
tree | 68afe60332674b4e737a07e03ebdc5b614454162 /drivers/soc | |
parent | 8483ff079d99e87c5da20767c0b894221019386f (diff) | |
parent | bbeaa59598cae12626c9c42e91821e3abd5b87f0 (diff) |
Merge tag 'qcom-soc-for-4.5' of git://git.kernel.org/pub/scm/linux/kernel/git/agross/linux into next/drivers
Qualcomm ARM Based SoC Updates for 4.5
* Add WCNSS_CTRL client
* Various Kconfig changes to fix build issues
* Update SoC Qualcomm MAINTAINERS entry
* Add SMP2P, SMSM, and SMEM state machine drivers
* Add SMD-RPM support for existing platforms
* tag 'qcom-soc-for-4.5' of git://git.kernel.org/pub/scm/linux/kernel/git/agross/linux:
MAINTAINERS: Change QCOM entries
soc: qcom: smd-rpm: Add existing platform support
soc: qcom: Introduce WCNSS_CTRL SMD client
ARM: qcom: select ARM_CPU_SUSPEND for power management
MAINTAINERS: Add rules for Qualcomm dts files
soc: qcom: enable smsm/smp2p modular build
serial: msm_serial: Make config tristate
soc: qcom: smp2p: Qualcomm Shared Memory Point to Point
soc: qcom: smsm: Add driver for Qualcomm SMSM
soc: qcom: Introduce common SMEM state machine code
Signed-off-by: Olof Johansson <olof@lixom.net>
Diffstat (limited to 'drivers/soc')
-rw-r--r-- | drivers/soc/qcom/Kconfig | 27 | ||||
-rw-r--r-- | drivers/soc/qcom/Makefile | 4 | ||||
-rw-r--r-- | drivers/soc/qcom/smd-rpm.c | 2 | ||||
-rw-r--r-- | drivers/soc/qcom/smem_state.c | 201 | ||||
-rw-r--r-- | drivers/soc/qcom/smp2p.c | 578 | ||||
-rw-r--r-- | drivers/soc/qcom/smsm.c | 625 | ||||
-rw-r--r-- | drivers/soc/qcom/wcnss_ctrl.c | 272 |
7 files changed, 1709 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index eec76141d9b9..461b387d03cc 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -13,6 +13,7 @@ config QCOM_GSBI config QCOM_PM bool "Qualcomm Power Management" depends on ARCH_QCOM && !ARM64 + select ARM_CPU_SUSPEND select QCOM_SCM help QCOM Platform specific power driver to manage cores and L2 low power @@ -49,3 +50,29 @@ config QCOM_SMD_RPM Say M here if you want to include support for the Qualcomm RPM as a module. This will build a module called "qcom-smd-rpm". + +config QCOM_SMEM_STATE + bool + +config QCOM_SMP2P + tristate "Qualcomm Shared Memory Point to Point support" + depends on QCOM_SMEM + select QCOM_SMEM_STATE + help + Say yes here to support the Qualcomm Shared Memory Point to Point + protocol. + +config QCOM_SMSM + tristate "Qualcomm Shared Memory State Machine" + depends on QCOM_SMEM + select QCOM_SMEM_STATE + help + Say yes here to support the Qualcomm Shared Memory State Machine. + The state machine is represented by bits in shared memory. + +config QCOM_WCNSS_CTRL + tristate "Qualcomm WCNSS control driver" + depends on QCOM_SMD + help + Client driver for the WCNSS_CTRL SMD channel, used to download nv + firmware to a newly booted WCNSS chip. diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 10a93d168e0e..fdd664edf0bd 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -3,3 +3,7 @@ obj-$(CONFIG_QCOM_PM) += spm.o obj-$(CONFIG_QCOM_SMD) += smd.o obj-$(CONFIG_QCOM_SMD_RPM) += smd-rpm.o obj-$(CONFIG_QCOM_SMEM) += smem.o +obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o +obj-$(CONFIG_QCOM_SMP2P) += smp2p.o +obj-$(CONFIG_QCOM_SMSM) += smsm.o +obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o diff --git a/drivers/soc/qcom/smd-rpm.c b/drivers/soc/qcom/smd-rpm.c index 2969321e1b09..731fa066f712 100644 --- a/drivers/soc/qcom/smd-rpm.c +++ b/drivers/soc/qcom/smd-rpm.c @@ -219,6 +219,8 @@ static void qcom_smd_rpm_remove(struct qcom_smd_device *sdev) } static const struct of_device_id qcom_smd_rpm_of_match[] = { + { .compatible = "qcom,rpm-apq8084" }, + { .compatible = "qcom,rpm-msm8916" }, { .compatible = "qcom,rpm-msm8974" }, {} }; diff --git a/drivers/soc/qcom/smem_state.c b/drivers/soc/qcom/smem_state.c new file mode 100644 index 000000000000..54261decb369 --- /dev/null +++ b/drivers/soc/qcom/smem_state.c @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2015, Sony Mobile Communications Inc. + * Copyright (c) 2012-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. + */ +#include <linux/device.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/soc/qcom/smem_state.h> + +static LIST_HEAD(smem_states); +static DEFINE_MUTEX(list_lock); + +/** + * struct qcom_smem_state - state context + * @refcount: refcount for the state + * @orphan: boolean indicator that this state has been unregistered + * @list: entry in smem_states list + * @of_node: of_node to use for matching the state in DT + * @priv: implementation private data + * @ops: ops for the state + */ +struct qcom_smem_state { + struct kref refcount; + bool orphan; + + struct list_head list; + struct device_node *of_node; + + void *priv; + + struct qcom_smem_state_ops ops; +}; + +/** + * qcom_smem_state_update_bits() - update the masked bits in state with value + * @state: state handle acquired by calling qcom_smem_state_get() + * @mask: bit mask for the change + * @value: new value for the masked bits + * + * Returns 0 on success, otherwise negative errno. + */ +int qcom_smem_state_update_bits(struct qcom_smem_state *state, + u32 mask, + u32 value) +{ + if (state->orphan) + return -ENXIO; + + if (!state->ops.update_bits) + return -ENOTSUPP; + + return state->ops.update_bits(state->priv, mask, value); +} +EXPORT_SYMBOL_GPL(qcom_smem_state_update_bits); + +static struct qcom_smem_state *of_node_to_state(struct device_node *np) +{ + struct qcom_smem_state *state; + + mutex_lock(&list_lock); + + list_for_each_entry(state, &smem_states, list) { + if (state->of_node == np) { + kref_get(&state->refcount); + goto unlock; + } + } + state = ERR_PTR(-EPROBE_DEFER); + +unlock: + mutex_unlock(&list_lock); + + return state; +} + +/** + * qcom_smem_state_get() - acquire handle to a state + * @dev: client device pointer + * @con_id: name of the state to lookup + * @bit: flags from the state reference, indicating which bit's affected + * + * Returns handle to the state, or ERR_PTR(). qcom_smem_state_put() must be + * called to release the returned state handle. + */ +struct qcom_smem_state *qcom_smem_state_get(struct device *dev, + const char *con_id, + unsigned *bit) +{ + struct qcom_smem_state *state; + struct of_phandle_args args; + int index = 0; + int ret; + + if (con_id) { + index = of_property_match_string(dev->of_node, + "qcom,state-names", + con_id); + if (index < 0) { + dev_err(dev, "missing qcom,state-names\n"); + return ERR_PTR(index); + } + } + + ret = of_parse_phandle_with_args(dev->of_node, + "qcom,state", + "#qcom,state-cells", + index, + &args); + if (ret) { + dev_err(dev, "failed to parse qcom,state property\n"); + return ERR_PTR(ret); + } + + if (args.args_count != 1) { + dev_err(dev, "invalid #qcom,state-cells\n"); + return ERR_PTR(-EINVAL); + } + + state = of_node_to_state(args.np); + if (IS_ERR(state)) + goto put; + + *bit = args.args[0]; + +put: + of_node_put(args.np); + return state; +} +EXPORT_SYMBOL_GPL(qcom_smem_state_get); + +static void qcom_smem_state_release(struct kref *ref) +{ + struct qcom_smem_state *state = container_of(ref, struct qcom_smem_state, refcount); + + list_del(&state->list); + kfree(state); +} + +/** + * qcom_smem_state_put() - release state handle + * @state: state handle to be released + */ +void qcom_smem_state_put(struct qcom_smem_state *state) +{ + mutex_lock(&list_lock); + kref_put(&state->refcount, qcom_smem_state_release); + mutex_unlock(&list_lock); +} +EXPORT_SYMBOL_GPL(qcom_smem_state_put); + +/** + * qcom_smem_state_register() - register a new state + * @of_node: of_node used for matching client lookups + * @ops: implementation ops + * @priv: implementation specific private data + */ +struct qcom_smem_state *qcom_smem_state_register(struct device_node *of_node, + const struct qcom_smem_state_ops *ops, + void *priv) +{ + struct qcom_smem_state *state; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return ERR_PTR(-ENOMEM); + + kref_init(&state->refcount); + + state->of_node = of_node; + state->ops = *ops; + state->priv = priv; + + mutex_lock(&list_lock); + list_add(&state->list, &smem_states); + mutex_unlock(&list_lock); + + return state; +} +EXPORT_SYMBOL_GPL(qcom_smem_state_register); + +/** + * qcom_smem_state_unregister() - unregister a registered state + * @state: state handle to be unregistered + */ +void qcom_smem_state_unregister(struct qcom_smem_state *state) +{ + state->orphan = true; + qcom_smem_state_put(state); +} +EXPORT_SYMBOL_GPL(qcom_smem_state_unregister); diff --git a/drivers/soc/qcom/smp2p.c b/drivers/soc/qcom/smp2p.c new file mode 100644 index 000000000000..f1eed7f9dd67 --- /dev/null +++ b/drivers/soc/qcom/smp2p.c @@ -0,0 +1,578 @@ +/* + * Copyright (c) 2015, Sony Mobile Communications AB. + * Copyright (c) 2012-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. + */ + +#include <linux/interrupt.h> +#include <linux/list.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/irq.h> +#include <linux/irqdomain.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <linux/soc/qcom/smem.h> +#include <linux/soc/qcom/smem_state.h> +#include <linux/spinlock.h> + +/* + * The Shared Memory Point to Point (SMP2P) protocol facilitates communication + * of a single 32-bit value between two processors. Each value has a single + * writer (the local side) and a single reader (the remote side). Values are + * uniquely identified in the system by the directed edge (local processor ID + * to remote processor ID) and a string identifier. + * + * Each processor is responsible for creating the outgoing SMEM items and each + * item is writable by the local processor and readable by the remote + * processor. By using two separate SMEM items that are single-reader and + * single-writer, SMP2P does not require any remote locking mechanisms. + * + * The driver uses the Linux GPIO and interrupt framework to expose a virtual + * GPIO for each outbound entry and a virtual interrupt controller for each + * inbound entry. + */ + +#define SMP2P_MAX_ENTRY 16 +#define SMP2P_MAX_ENTRY_NAME 16 + +#define SMP2P_FEATURE_SSR_ACK 0x1 + +#define SMP2P_MAGIC 0x504d5324 + +/** + * struct smp2p_smem_item - in memory communication structure + * @magic: magic number + * @version: version - must be 1 + * @features: features flag - currently unused + * @local_pid: processor id of sending end + * @remote_pid: processor id of receiving end + * @total_entries: number of entries - always SMP2P_MAX_ENTRY + * @valid_entries: number of allocated entries + * @flags: + * @entries: individual communication entries + * @name: name of the entry + * @value: content of the entry + */ +struct smp2p_smem_item { + u32 magic; + u8 version; + unsigned features:24; + u16 local_pid; + u16 remote_pid; + u16 total_entries; + u16 valid_entries; + u32 flags; + + struct { + u8 name[SMP2P_MAX_ENTRY_NAME]; + u32 value; + } entries[SMP2P_MAX_ENTRY]; +} __packed; + +/** + * struct smp2p_entry - driver context matching one entry + * @node: list entry to keep track of allocated entries + * @smp2p: reference to the device driver context + * @name: name of the entry, to match against smp2p_smem_item + * @value: pointer to smp2p_smem_item entry value + * @last_value: last handled value + * @domain: irq_domain for inbound entries + * @irq_enabled:bitmap to track enabled irq bits + * @irq_rising: bitmap to mark irq bits for rising detection + * @irq_falling:bitmap to mark irq bits for falling detection + * @state: smem state handle + * @lock: spinlock to protect read-modify-write of the value + */ +struct smp2p_entry { + struct list_head node; + struct qcom_smp2p *smp2p; + + const char *name; + u32 *value; + u32 last_value; + + struct irq_domain *domain; + DECLARE_BITMAP(irq_enabled, 32); + DECLARE_BITMAP(irq_rising, 32); + DECLARE_BITMAP(irq_falling, 32); + + struct qcom_smem_state *state; + + spinlock_t lock; +}; + +#define SMP2P_INBOUND 0 +#define SMP2P_OUTBOUND 1 + +/** + * struct qcom_smp2p - device driver context + * @dev: device driver handle + * @in: pointer to the inbound smem item + * @smem_items: ids of the two smem items + * @valid_entries: already scanned inbound entries + * @local_pid: processor id of the inbound edge + * @remote_pid: processor id of the outbound edge + * @ipc_regmap: regmap for the outbound ipc + * @ipc_offset: offset within the regmap + * @ipc_bit: bit in regmap@offset to kick to signal remote processor + * @inbound: list of inbound entries + * @outbound: list of outbound entries + */ +struct qcom_smp2p { + struct device *dev; + + struct smp2p_smem_item *in; + struct smp2p_smem_item *out; + + unsigned smem_items[SMP2P_OUTBOUND + 1]; + + unsigned valid_entries; + + unsigned local_pid; + unsigned remote_pid; + + struct regmap *ipc_regmap; + int ipc_offset; + int ipc_bit; + + struct list_head inbound; + struct list_head outbound; +}; + +static void qcom_smp2p_kick(struct qcom_smp2p *smp2p) +{ + /* Make sure any updated data is written before the kick */ + wmb(); + regmap_write(smp2p->ipc_regmap, smp2p->ipc_offset, BIT(smp2p->ipc_bit)); +} + +/** + * qcom_smp2p_intr() - interrupt handler for incoming notifications + * @irq: unused + * @data: smp2p driver context + * + * Handle notifications from the remote side to handle newly allocated entries + * or any changes to the state bits of existing entries. + */ +static irqreturn_t qcom_smp2p_intr(int irq, void *data) +{ + struct smp2p_smem_item *in; + struct smp2p_entry *entry; + struct qcom_smp2p *smp2p = data; + unsigned smem_id = smp2p->smem_items[SMP2P_INBOUND]; + unsigned pid = smp2p->remote_pid; + size_t size; + int irq_pin; + u32 status; + char buf[SMP2P_MAX_ENTRY_NAME]; + u32 val; + int i; + + in = smp2p->in; + + /* Acquire smem item, if not already found */ + if (!in) { + in = qcom_smem_get(pid, smem_id, &size); + if (IS_ERR(in)) { + dev_err(smp2p->dev, + "Unable to acquire remote smp2p item\n"); + return IRQ_HANDLED; + } + + smp2p->in = in; + } + + /* Match newly created entries */ + for (i = smp2p->valid_entries; i < in->valid_entries; i++) { + list_for_each_entry(entry, &smp2p->inbound, node) { + memcpy_fromio(buf, in->entries[i].name, sizeof(buf)); + if (!strcmp(buf, entry->name)) { + entry->value = &in->entries[i].value; + break; + } + } + } + smp2p->valid_entries = i; + + /* Fire interrupts based on any value changes */ + list_for_each_entry(entry, &smp2p->inbound, node) { + /* Ignore entries not yet allocated by the remote side */ + if (!entry->value) + continue; + + val = readl(entry->value); + + status = val ^ entry->last_value; + entry->last_value = val; + + /* No changes of this entry? */ + if (!status) + continue; + + for_each_set_bit(i, entry->irq_enabled, 32) { + if (!(status & BIT(i))) + continue; + + if ((val & BIT(i) && test_bit(i, entry->irq_rising)) || + (!(val & BIT(i)) && test_bit(i, entry->irq_falling))) { + irq_pin = irq_find_mapping(entry->domain, i); + handle_nested_irq(irq_pin); + } + } + } + + return IRQ_HANDLED; +} + +static void smp2p_mask_irq(struct irq_data *irqd) +{ + struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t irq = irqd_to_hwirq(irqd); + + clear_bit(irq, entry->irq_enabled); +} + +static void smp2p_unmask_irq(struct irq_data *irqd) +{ + struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t irq = irqd_to_hwirq(irqd); + + set_bit(irq, entry->irq_enabled); +} + +static int smp2p_set_irq_type(struct irq_data *irqd, unsigned int type) +{ + struct smp2p_entry *entry = irq_data_get_irq_chip_data(irqd); + irq_hw_number_t irq = irqd_to_hwirq(irqd); + + if (!(type & IRQ_TYPE_EDGE_BOTH)) + return -EINVAL; + + if (type & IRQ_TYPE_EDGE_RISING) + set_bit(irq, entry->irq_rising); + else + clear_bit(irq, entry->irq_rising); + + if (type & IRQ_TYPE_EDGE_FALLING) + set_bit(irq, entry->irq_falling); + else + clear_bit(irq, entry->irq_falling); + + return 0; +} + +static struct irq_chip smp2p_irq_chip = { + .name = "smp2p", + .irq_mask = smp2p_mask_irq, + .irq_unmask = smp2p_unmask_irq, + .irq_set_type = smp2p_set_irq_type, +}; + +static int smp2p_irq_map(struct irq_domain *d, + unsigned int irq, + irq_hw_number_t hw) +{ + struct smp2p_entry *entry = d->host_data; + + irq_set_chip_and_handler(irq, &smp2p_irq_chip, handle_level_irq); + irq_set_chip_data(irq, entry); + irq_set_nested_thread(irq, 1); + irq_set_noprobe(irq); + + return 0; +} + +static const struct irq_domain_ops smp2p_irq_ops = { + .map = smp2p_irq_map, + .xlate = irq_domain_xlate_twocell, +}; + +static int qcom_smp2p_inbound_entry(struct qcom_smp2p *smp2p, + struct smp2p_entry *entry, + struct device_node *node) +{ + entry->domain = irq_domain_add_linear(node, 32, &smp2p_irq_ops, entry); + if (!entry->domain) { + dev_err(smp2p->dev, "failed to add irq_domain\n"); + return -ENOMEM; + } + + return 0; +} + +static int smp2p_update_bits(void *data, u32 mask, u32 value) +{ + struct smp2p_entry *entry = data; + u32 orig; + u32 val; + + spin_lock(&entry->lock); + val = orig = readl(entry->value); + val &= ~mask; + val |= value; + writel(val, entry->value); + spin_unlock(&entry->lock); + + if (val != orig) + qcom_smp2p_kick(entry->smp2p); + + return 0; +} + +static const struct qcom_smem_state_ops smp2p_state_ops = { + .update_bits = smp2p_update_bits, +}; + +static int qcom_smp2p_outbound_entry(struct qcom_smp2p *smp2p, + struct smp2p_entry *entry, + struct device_node *node) +{ + struct smp2p_smem_item *out = smp2p->out; + char buf[SMP2P_MAX_ENTRY_NAME] = {}; + + /* Allocate an entry from the smem item */ + strlcpy(buf, entry->name, SMP2P_MAX_ENTRY_NAME); + memcpy_toio(out->entries[out->valid_entries].name, buf, SMP2P_MAX_ENTRY_NAME); + out->valid_entries++; + + /* Make the logical entry reference the physical value */ + entry->value = &out->entries[out->valid_entries].value; + + entry->state = qcom_smem_state_register(node, &smp2p_state_ops, entry); + if (IS_ERR(entry->state)) { + dev_err(smp2p->dev, "failed to register qcom_smem_state\n"); + return PTR_ERR(entry->state); + } + + return 0; +} + +static int qcom_smp2p_alloc_outbound_item(struct qcom_smp2p *smp2p) +{ + struct smp2p_smem_item *out; + unsigned smem_id = smp2p->smem_items[SMP2P_OUTBOUND]; + unsigned pid = smp2p->remote_pid; + int ret; + + ret = qcom_smem_alloc(pid, smem_id, sizeof(*out)); + if (ret < 0 && ret != -EEXIST) { + if (ret != -EPROBE_DEFER) + dev_err(smp2p->dev, + "unable to allocate local smp2p item\n"); + return ret; + } + + out = qcom_smem_get(pid, smem_id, NULL); + if (IS_ERR(out)) { + dev_err(smp2p->dev, "Unable to acquire local smp2p item\n"); + return PTR_ERR(out); + } + + memset(out, 0, sizeof(*out)); + out->magic = SMP2P_MAGIC; + out->local_pid = smp2p->local_pid; + out->remote_pid = smp2p->remote_pid; + out->total_entries = SMP2P_MAX_ENTRY; + out->valid_entries = 0; + + /* + * Make sure the rest of the header is written before we validate the + * item by writing a valid version number. + */ + wmb(); + out->version = 1; + + qcom_smp2p_kick(smp2p); + + smp2p->out = out; + + return 0; +} + +static int smp2p_parse_ipc(struct qcom_smp2p *smp2p) +{ + struct device_node *syscon; + struct device *dev = smp2p->dev; + const char *key; + int ret; + + syscon = of_parse_phandle(dev->of_node, "qcom,ipc", 0); + if (!syscon) { + dev_err(dev, "no qcom,ipc node\n"); + return -ENODEV; + } + + smp2p->ipc_regmap = syscon_node_to_regmap(syscon); + if (IS_ERR(smp2p->ipc_regmap)) + return PTR_ERR(smp2p->ipc_regmap); + + key = "qcom,ipc"; + ret = of_property_read_u32_index(dev->of_node, key, 1, &smp2p->ipc_offset); + if (ret < 0) { + dev_err(dev, "no offset in %s\n", key); + return -EINVAL; + } + + ret = of_property_read_u32_index(dev->of_node, key, 2, &smp2p->ipc_bit); + if (ret < 0) { + dev_err(dev, "no bit in %s\n", key); + return -EINVAL; + } + + return 0; +} + +static int qcom_smp2p_probe(struct platform_device *pdev) +{ + struct smp2p_entry *entry; + struct device_node *node; + struct qcom_smp2p *smp2p; + const char *key; + int irq; + int ret; + + smp2p = devm_kzalloc(&pdev->dev, sizeof(*smp2p), GFP_KERNEL); + if (!smp2p) + return -ENOMEM; + + smp2p->dev = &pdev->dev; + INIT_LIST_HEAD(&smp2p->inbound); + INIT_LIST_HEAD(&smp2p->outbound); + + platform_set_drvdata(pdev, smp2p); + + ret = smp2p_parse_ipc(smp2p); + if (ret) + return ret; + + key = "qcom,smem"; + ret = of_property_read_u32_array(pdev->dev.of_node, key, + smp2p->smem_items, 2); + if (ret) + return ret; + + key = "qcom,local-pid"; + ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->local_pid); + if (ret < 0) { + dev_err(&pdev->dev, "failed to read %s\n", key); + return -EINVAL; + } + + key = "qcom,remote-pid"; + ret = of_property_read_u32(pdev->dev.of_node, key, &smp2p->remote_pid); + if (ret < 0) { + dev_err(&pdev->dev, "failed to read %s\n", key); + return -EINVAL; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "unable to acquire smp2p interrupt\n"); + return irq; + } + + ret = qcom_smp2p_alloc_outbound_item(smp2p); + if (ret < 0) + return ret; + + for_each_available_child_of_node(pdev->dev.of_node, node) { + entry = devm_kzalloc(&pdev->dev, sizeof(*entry), GFP_KERNEL); + if (!entry) { + ret = -ENOMEM; + goto unwind_interfaces; + } + + entry->smp2p = smp2p; + spin_lock_init(&entry->lock); + + ret = of_property_read_string(node, "qcom,entry-name", &entry->name); + if (ret < 0) + goto unwind_interfaces; + + if (of_property_read_bool(node, "interrupt-controller")) { + ret = qcom_smp2p_inbound_entry(smp2p, entry, node); + if (ret < 0) + goto unwind_interfaces; + + list_add(&entry->node, &smp2p->inbound); + } else { + ret = qcom_smp2p_outbound_entry(smp2p, entry, node); + if (ret < 0) + goto unwind_interfaces; + + list_add(&entry->node, &smp2p->outbound); + } + } + + /* Kick the outgoing edge after allocating entries */ + qcom_smp2p_kick(smp2p); + + ret = devm_request_threaded_irq(&pdev->dev, irq, + NULL, qcom_smp2p_intr, + IRQF_ONESHOT, + "smp2p", (void *)smp2p); + if (ret) { + dev_err(&pdev->dev, "failed to request interrupt\n"); + goto unwind_interfaces; + } + + + return 0; + +unwind_interfaces: + list_for_each_entry(entry, &smp2p->inbound, node) + irq_domain_remove(entry->domain); + + list_for_each_entry(entry, &smp2p->outbound, node) + qcom_smem_state_unregister(entry->state); + + smp2p->out->valid_entries = 0; + + return ret; +} + +static int qcom_smp2p_remove(struct platform_device *pdev) +{ + struct qcom_smp2p *smp2p = platform_get_drvdata(pdev); + struct smp2p_entry *entry; + + list_for_each_entry(entry, &smp2p->inbound, node) + irq_domain_remove(entry->domain); + + list_for_each_entry(entry, &smp2p->outbound, node) + qcom_smem_state_unregister(entry->state); + + smp2p->out->valid_entries = 0; + + return 0; +} + +static const struct of_device_id qcom_smp2p_of_match[] = { + { .compatible = "qcom,smp2p" }, + {} +}; +MODULE_DEVICE_TABLE(of, qcom_smp2p_of_match); + +static struct platform_driver qcom_smp2p_driver = { + .probe = qcom_smp2p_probe, + .remove = qcom_smp2p_remove, + .driver = { + .name = "qcom_smp2p", + .of_match_table = qcom_smp2p_of_match, + }, +}; +module_platform_driver(qcom_smp2p_driver); + +MODULE_DESCRIPTION("Qualcomm Shared Memory Point to Point driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soc/qcom/smsm.c b/drivers/soc/qcom/smsm.c new file mode 100644 index 000000000000..6b777af1bc19 --- /dev/null +++ b/drivers/soc/qcom/smsm.c @@ -0,0 +1,625 @@ +/* + * Copyright (c) 2015, Sony Mobile Communications Inc. + * Copyright (c) 2012-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. + */ + +#include <linux/interrupt.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_irq.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/regmap.h> +#include <linux/soc/qcom/smem.h> +#include <linux/soc/qcom/smem_state.h> + +/* + * This driver implements the Qualcomm Shared Memory State Machine, a mechanism + * for communicating single bit state information to remote processors. + * + * The implementation is based on two sections of shared memory; the first + * holding the state bits and the second holding a matrix of subscription bits. + * + * The state bits are structured in entries of 32 bits, each belonging to one + * system in the SoC. The entry belonging to the local system is considered + * read-write, while the rest should be considered read-only. + * + * The subscription matrix consists of N bitmaps per entry, denoting interest + * in updates of the entry for each of the N hosts. Upon updating a state bit + * each host's subscription bitmap should be queried and the remote system + * should be interrupted if they request so. + * + * The subscription matrix is laid out in entry-major order: + * entry0: [host0 ... hostN] + * . + * . + * entryM: [host0 ... hostN] + * + * A third, optional, shared memory region might contain information regarding + * the number of entries in the state bitmap as well as number of columns in + * the subscription matrix. + */ + +/* + * Shared memory identifiers, used to acquire handles to respective memory + * region. + */ +#define SMEM_SMSM_SHARED_STATE 85 +#define SMEM_SMSM_CPU_INTR_MASK 333 +#define SMEM_SMSM_SIZE_INFO 419 + +/* + * Default sizes, in case SMEM_SMSM_SIZE_INFO is not found. + */ +#define SMSM_DEFAULT_NUM_ENTRIES 8 +#define SMSM_DEFAULT_NUM_HOSTS 3 + +struct smsm_entry; +struct smsm_host; + +/** + * struct qcom_smsm - smsm driver context + * @dev: smsm device pointer + * @local_host: column in the subscription matrix representing this system + * @num_hosts: number of columns in the subscription matrix + * @num_entries: number of entries in the state map and rows in the subscription + * matrix + * @local_state: pointer to the local processor's state bits + * @subscription: pointer to local processor's row in subscription matrix + * @state: smem state handle + * @lock: spinlock for read-modify-write of the outgoing state + * @entries: context for each of the entries + * @hosts: context for each of the hosts + */ +struct qcom_smsm { + struct device *dev; + + u32 local_host; + + u32 num_hosts; + u32 num_entries; + + u32 *local_state; + u32 *subscription; + struct qcom_smem_state *state; + + spinlock_t lock; + + struct smsm_entry *entries; + struct smsm_host *hosts; +}; + +/** + * struct smsm_entry - per remote processor entry context + * @smsm: back-reference to driver context + * @domain: IRQ domain for this entry, if representing a remote system + * @irq_enabled: bitmap of which state bits IRQs are enabled + * @irq_rising: bitmap tracking if rising bits should be propagated + * @irq_falling: bitmap tracking if falling bits should be propagated + * @last_value: snapshot of state bits last time the interrupts where propagated + * @remote_state: pointer to this entry's state bits + * @subscription: pointer to a row in the subscription matrix representing this + * entry + */ +struct smsm_entry { + struct qcom_smsm *smsm; + + struct irq_domain *domain; + DECLARE_BITMAP(irq_enabled, 32); + DECLARE_BITMAP(irq_rising, 32); + DECLARE_BITMAP(irq_falling, 32); + u32 last_value; + + u32 *remote_state; + u32 *subscription; +}; + +/** + * struct smsm_host - representation of a remote host + * @ipc_regmap: regmap for outgoing interrupt + * @ipc_offset: offset in @ipc_regmap for outgoing interrupt + * @ipc_bit: bit in @ipc_regmap + @ipc_offset for outgoing interrupt + */ +struct smsm_host { + struct regmap *ipc_regmap; + int ipc_offset; + int ipc_bit; +}; + +/** + * smsm_update_bits() - change bit in outgoing entry and inform subscribers + * @data: smsm context pointer + * @offset: bit in the entry + * @value: new value + * + * Used to set and clear the bits in the outgoing/local entry and inform + * subscribers about the change. + */ +static int smsm_update_bits(void *data, u32 mask, u32 value) +{ + struct qcom_smsm *smsm = data; + struct smsm_host *hostp; + unsigned long flags; + u32 changes; + u32 host; + u32 orig; + u32 val; + + spin_lock_irqsave(&smsm->lock, flags); + + /* Update the entry */ + val = orig = readl(smsm->local_state); + val &= ~mask; + val |= value; + + /* Don't signal if we didn't change the value */ + changes = val ^ orig; + if (!changes) { + spin_unlock_irqrestore(&smsm->lock, flags); + goto done; + } + + /* Write out the new value */ + writel(val, smsm->local_state); + spin_unlock_irqrestore(&smsm->lock, flags); + + /* Make sure the value update is ordered before any kicks */ + wmb(); + + /* Iterate over all hosts to check whom wants a kick */ + for (host = 0; host < smsm->num_hosts; host++) { + hostp = &smsm->hosts[host]; + + val = readl(smsm->subscription + host); + if (val & changes && hostp->ipc_regmap) { + regmap_write(hostp->ipc_regmap, + hostp->ipc_offset, + BIT(hostp->ipc_bit)); + } + } + +done: + return 0; +} + +static const struct qcom_smem_state_ops smsm_state_ops = { + .update_bits = smsm_update_bits, +}; + +/** + * smsm_intr() - cascading IRQ handler for SMSM + * @irq: unused + * @data: entry related to this IRQ + * + * This function cascades an incoming interrupt from a remote system, based on + * the state bits and configuration. + */ +static irqreturn_t smsm_intr(int irq, void *data) +{ + struct smsm_entry *entry = data; + unsigned i; + int irq_pin; + u32 changed; + u32 val; + + val = readl(entry->remote_state); + changed = val ^ entry->last_value; + entry->last_value = val; + + for_each_set_bit(i, entry->irq_enabled, 32) { + if (!(changed & BIT(i))) + continue; + + if (val & BIT(i)) { + if (test_bit(i, entry->irq_rising)) { + irq_pin = irq_find_mapping(entry->domain, i); + handle_nested_irq(irq_pin); + } + } else { + if (test_bit(i, entry->irq_falling)) { + irq_pin = irq_find_mapping(entry->domain, i); + handle_nested_irq(irq_ |