// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015, Sony Mobile Communications Inc. * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
*/
/* * 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 * @mbox_client: mailbox client handle
*/ struct qcom_smsm { struct device *dev;
/** * 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 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 * @mbox_chan: apcs ipc mailbox channel handle
*/ struct smsm_host { struct regmap *ipc_regmap; int ipc_offset; int ipc_bit;
struct mbox_chan *mbox_chan;
};
/** * smsm_update_bits() - change bit in outgoing entry and inform subscribers * @data: smsm context pointer * @mask: value mask * @value: new value * * Used to set and clear the bits in the outgoing/local entry and inform * subscribers about the change.
*/ staticint smsm_update_bits(void *data, u32 mask, u32 value)
{ struct qcom_smsm *smsm = data; struct smsm_host *hostp; unsignedlong 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)) continue;
/** * 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 ^ xchg(&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_pin);
}
}
}
return IRQ_HANDLED;
}
/** * smsm_mask_irq() - un-subscribe from cascades of IRQs of a certain staus bit * @irqd: IRQ handle to be masked * * This un-subscribes the local CPU from interrupts upon changes to the defines * status bit. The bit is also cleared from cascading.
*/ staticvoid smsm_mask_irq(struct irq_data *irqd)
{ struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
irq_hw_number_t irq = irqd_to_hwirq(irqd); struct qcom_smsm *smsm = entry->smsm;
u32 val;
if (entry->subscription) {
val = readl(entry->subscription + smsm->local_host);
val &= ~BIT(irq);
writel(val, entry->subscription + smsm->local_host);
}
clear_bit(irq, entry->irq_enabled);
}
/** * smsm_unmask_irq() - subscribe to cascades of IRQs of a certain status bit * @irqd: IRQ handle to be unmasked * * This subscribes the local CPU to interrupts upon changes to the defined * status bit. The bit is also marked for cascading.
*/ staticvoid smsm_unmask_irq(struct irq_data *irqd)
{ struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
irq_hw_number_t irq = irqd_to_hwirq(irqd); struct qcom_smsm *smsm = entry->smsm;
u32 val;
/* Make sure our last cached state is up-to-date */ if (readl(entry->remote_state) & BIT(irq))
set_bit(irq, &entry->last_value); else
clear_bit(irq, &entry->last_value);
set_bit(irq, entry->irq_enabled);
if (entry->subscription) {
val = readl(entry->subscription + smsm->local_host);
val |= BIT(irq);
writel(val, entry->subscription + smsm->local_host);
}
}
/** * smsm_set_irq_type() - updates the requested IRQ type for the cascading * @irqd: consumer interrupt handle * @type: requested flags
*/ staticint smsm_set_irq_type(struct irq_data *irqd, unsignedint type)
{ struct smsm_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);
/** * smsm_parse_mbox() - requests an mbox channel * @smsm: smsm driver context * @host_id: index of the remote host to be resolved * * Requests the desired channel using the mbox interface which is needed for * sending the outgoing interrupts to a remove hosts - identified by @host_id.
*/ staticint smsm_parse_mbox(struct qcom_smsm *smsm, unsignedint host_id)
{ struct smsm_host *host = &smsm->hosts[host_id]; int ret = 0;
host->mbox_chan = mbox_request_channel(&smsm->mbox_client, host_id); if (IS_ERR(host->mbox_chan)) {
ret = PTR_ERR(host->mbox_chan);
host->mbox_chan = NULL;
}
return ret;
}
/** * smsm_parse_ipc() - parses a qcom,ipc-%d device tree property * @smsm: smsm driver context * @host_id: index of the remote host to be resolved * * Parses device tree to acquire the information needed for sending the * outgoing interrupts to a remote host - identified by @host_id.
*/ staticint smsm_parse_ipc(struct qcom_smsm *smsm, unsigned host_id)
{ struct device_node *syscon; struct device_node *node = smsm->dev->of_node; struct smsm_host *host = &smsm->hosts[host_id]; char key[16]; int ret;
host->ipc_regmap = syscon_node_to_regmap(syscon);
of_node_put(syscon); if (IS_ERR(host->ipc_regmap)) return PTR_ERR(host->ipc_regmap);
ret = of_property_read_u32_index(node, key, 1, &host->ipc_offset); if (ret < 0) {
dev_err(smsm->dev, "no offset in %s\n", key); return -EINVAL;
}
ret = of_property_read_u32_index(node, key, 2, &host->ipc_bit); if (ret < 0) {
dev_err(smsm->dev, "no bit in %s\n", key); return -EINVAL;
}
return 0;
}
/** * smsm_inbound_entry() - parse DT and set up an entry representing a remote system * @smsm: smsm driver context * @entry: entry context to be set up * @node: dt node containing the entry's properties
*/ staticint smsm_inbound_entry(struct qcom_smsm *smsm, struct smsm_entry *entry, struct device_node *node)
{ int ret; int irq;
irq = irq_of_parse_and_map(node, 0); if (!irq) {
dev_err(smsm->dev, "failed to parse smsm interrupt\n"); return -EINVAL;
}
ret = devm_request_threaded_irq(smsm->dev, irq,
NULL, smsm_intr,
IRQF_ONESHOT, "smsm", (void *)entry); if (ret) {
dev_err(smsm->dev, "failed to request interrupt\n"); return ret;
}
entry->domain = irq_domain_create_linear(of_fwnode_handle(node), 32, &smsm_irq_ops, entry); if (!entry->domain) {
dev_err(smsm->dev, "failed to add irq_domain\n"); return -ENOMEM;
}
return 0;
}
/** * smsm_get_size_info() - parse the optional memory segment for sizes * @smsm: smsm driver context * * Attempt to acquire the number of hosts and entries from the optional shared * memory location. Not being able to find this segment should indicate that * we're on a older system where these values was hard coded to * SMSM_DEFAULT_NUM_ENTRIES and SMSM_DEFAULT_NUM_HOSTS. * * Returns 0 on success, negative errno on failure.
*/ staticint smsm_get_size_info(struct qcom_smsm *smsm)
{
size_t size; struct {
u32 num_hosts;
u32 num_entries;
u32 reserved0;
u32 reserved1;
} *info;
info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SIZE_INFO, &size); if (IS_ERR(info) && PTR_ERR(info) != -ENOENT) return dev_err_probe(smsm->dev, PTR_ERR(info), "unable to retrieve smsm size info\n"); elseif (IS_ERR(info) || size != sizeof(*info)) {
dev_warn(smsm->dev, "no smsm size info, using defaults\n");
smsm->num_entries = SMSM_DEFAULT_NUM_ENTRIES;
smsm->num_hosts = SMSM_DEFAULT_NUM_HOSTS; return 0;
}
for_each_child_of_node(pdev->dev.of_node, local_node) { if (of_property_present(local_node, "#qcom,smem-state-cells")) break;
} if (!local_node) {
dev_err(&pdev->dev, "no state entry\n"); return -EINVAL;
}
/* Parse the host properties */ for (id = 0; id < smsm->num_hosts; id++) { /* Try using mbox interface first, otherwise fall back to syscon */
ret = smsm_parse_mbox(smsm, id); if (!ret) continue;
ret = smsm_parse_ipc(smsm, id); if (ret < 0) goto out_put;
}
/* Acquire the main SMSM state vector */
ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE,
smsm->num_entries * sizeof(u32)); if (ret < 0 && ret != -EEXIST) {
dev_err(&pdev->dev, "unable to allocate shared state entry\n"); goto out_put;
}
states = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE, NULL); if (IS_ERR(states)) {
dev_err(&pdev->dev, "Unable to acquire shared state entry\n");
ret = PTR_ERR(states); goto out_put;
}
/* Acquire the list of interrupt mask vectors */
size = smsm->num_entries * smsm->num_hosts * sizeof(u32);
ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, size); if (ret < 0 && ret != -EEXIST) {
dev_err(&pdev->dev, "unable to allocate smsm interrupt mask\n"); goto out_put;
}
intr_mask = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, NULL); if (IS_ERR(intr_mask)) {
dev_err(&pdev->dev, "unable to acquire shared memory interrupt mask\n");
ret = PTR_ERR(intr_mask); goto out_put;
}
/* Setup the reference to the local state bits */
smsm->local_state = states + smsm->local_host;
smsm->subscription = intr_mask + smsm->local_host * smsm->num_hosts;
/* Register the outgoing state */
smsm->state = qcom_smem_state_register(local_node, &smsm_state_ops, smsm); if (IS_ERR(smsm->state)) {
dev_err(smsm->dev, "failed to register qcom_smem_state\n");
ret = PTR_ERR(smsm->state); goto out_put;
}
/* Register handlers for remote processor entries of interest. */
for_each_available_child_of_node(pdev->dev.of_node, node) { if (!of_property_read_bool(node, "interrupt-controller")) continue;
ret = of_property_read_u32(node, "reg", &id); if (ret || id >= smsm->num_entries) {
dev_err(&pdev->dev, "invalid reg of entry\n"); if (!ret)
ret = -EINVAL; goto unwind_interfaces;
}
entry = &smsm->entries[id];
entry->smsm = smsm;
entry->remote_state = states + id;
/* Setup subscription pointers and unsubscribe to any kicks */
entry->subscription = intr_mask + id * smsm->num_hosts;
writel(0, entry->subscription + smsm->local_host);
ret = smsm_inbound_entry(smsm, entry, node); if (ret < 0) goto unwind_interfaces;
}
unwind_interfaces:
of_node_put(node); for (id = 0; id < smsm->num_entries; id++) if (smsm->entries[id].domain)
irq_domain_remove(smsm->entries[id].domain);
qcom_smem_state_unregister(smsm->state);
out_put: for (id = 0; id < smsm->num_hosts; id++)
mbox_free_channel(smsm->hosts[id].mbox_chan);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.