18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2015, Sony Mobile Communications Inc.
48c2ecf20Sopenharmony_ci * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
88c2ecf20Sopenharmony_ci#include <linux/mfd/syscon.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/of_irq.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
138c2ecf20Sopenharmony_ci#include <linux/regmap.h>
148c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem.h>
158c2ecf20Sopenharmony_ci#include <linux/soc/qcom/smem_state.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci/*
188c2ecf20Sopenharmony_ci * This driver implements the Qualcomm Shared Memory State Machine, a mechanism
198c2ecf20Sopenharmony_ci * for communicating single bit state information to remote processors.
208c2ecf20Sopenharmony_ci *
218c2ecf20Sopenharmony_ci * The implementation is based on two sections of shared memory; the first
228c2ecf20Sopenharmony_ci * holding the state bits and the second holding a matrix of subscription bits.
238c2ecf20Sopenharmony_ci *
248c2ecf20Sopenharmony_ci * The state bits are structured in entries of 32 bits, each belonging to one
258c2ecf20Sopenharmony_ci * system in the SoC. The entry belonging to the local system is considered
268c2ecf20Sopenharmony_ci * read-write, while the rest should be considered read-only.
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci * The subscription matrix consists of N bitmaps per entry, denoting interest
298c2ecf20Sopenharmony_ci * in updates of the entry for each of the N hosts. Upon updating a state bit
308c2ecf20Sopenharmony_ci * each host's subscription bitmap should be queried and the remote system
318c2ecf20Sopenharmony_ci * should be interrupted if they request so.
328c2ecf20Sopenharmony_ci *
338c2ecf20Sopenharmony_ci * The subscription matrix is laid out in entry-major order:
348c2ecf20Sopenharmony_ci * entry0: [host0 ... hostN]
358c2ecf20Sopenharmony_ci *	.
368c2ecf20Sopenharmony_ci *	.
378c2ecf20Sopenharmony_ci * entryM: [host0 ... hostN]
388c2ecf20Sopenharmony_ci *
398c2ecf20Sopenharmony_ci * A third, optional, shared memory region might contain information regarding
408c2ecf20Sopenharmony_ci * the number of entries in the state bitmap as well as number of columns in
418c2ecf20Sopenharmony_ci * the subscription matrix.
428c2ecf20Sopenharmony_ci */
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_ci/*
458c2ecf20Sopenharmony_ci * Shared memory identifiers, used to acquire handles to respective memory
468c2ecf20Sopenharmony_ci * region.
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_ci#define SMEM_SMSM_SHARED_STATE		85
498c2ecf20Sopenharmony_ci#define SMEM_SMSM_CPU_INTR_MASK		333
508c2ecf20Sopenharmony_ci#define SMEM_SMSM_SIZE_INFO		419
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/*
538c2ecf20Sopenharmony_ci * Default sizes, in case SMEM_SMSM_SIZE_INFO is not found.
548c2ecf20Sopenharmony_ci */
558c2ecf20Sopenharmony_ci#define SMSM_DEFAULT_NUM_ENTRIES	8
568c2ecf20Sopenharmony_ci#define SMSM_DEFAULT_NUM_HOSTS		3
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_cistruct smsm_entry;
598c2ecf20Sopenharmony_cistruct smsm_host;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci/**
628c2ecf20Sopenharmony_ci * struct qcom_smsm - smsm driver context
638c2ecf20Sopenharmony_ci * @dev:	smsm device pointer
648c2ecf20Sopenharmony_ci * @local_host:	column in the subscription matrix representing this system
658c2ecf20Sopenharmony_ci * @num_hosts:	number of columns in the subscription matrix
668c2ecf20Sopenharmony_ci * @num_entries: number of entries in the state map and rows in the subscription
678c2ecf20Sopenharmony_ci *		matrix
688c2ecf20Sopenharmony_ci * @local_state: pointer to the local processor's state bits
698c2ecf20Sopenharmony_ci * @subscription: pointer to local processor's row in subscription matrix
708c2ecf20Sopenharmony_ci * @state:	smem state handle
718c2ecf20Sopenharmony_ci * @lock:	spinlock for read-modify-write of the outgoing state
728c2ecf20Sopenharmony_ci * @entries:	context for each of the entries
738c2ecf20Sopenharmony_ci * @hosts:	context for each of the hosts
748c2ecf20Sopenharmony_ci */
758c2ecf20Sopenharmony_cistruct qcom_smsm {
768c2ecf20Sopenharmony_ci	struct device *dev;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	u32 local_host;
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	u32 num_hosts;
818c2ecf20Sopenharmony_ci	u32 num_entries;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	u32 *local_state;
848c2ecf20Sopenharmony_ci	u32 *subscription;
858c2ecf20Sopenharmony_ci	struct qcom_smem_state *state;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	spinlock_t lock;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	struct smsm_entry *entries;
908c2ecf20Sopenharmony_ci	struct smsm_host *hosts;
918c2ecf20Sopenharmony_ci};
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci/**
948c2ecf20Sopenharmony_ci * struct smsm_entry - per remote processor entry context
958c2ecf20Sopenharmony_ci * @smsm:	back-reference to driver context
968c2ecf20Sopenharmony_ci * @domain:	IRQ domain for this entry, if representing a remote system
978c2ecf20Sopenharmony_ci * @irq_enabled: bitmap of which state bits IRQs are enabled
988c2ecf20Sopenharmony_ci * @irq_rising:	bitmap tracking if rising bits should be propagated
998c2ecf20Sopenharmony_ci * @irq_falling: bitmap tracking if falling bits should be propagated
1008c2ecf20Sopenharmony_ci * @last_value:	snapshot of state bits last time the interrupts where propagated
1018c2ecf20Sopenharmony_ci * @remote_state: pointer to this entry's state bits
1028c2ecf20Sopenharmony_ci * @subscription: pointer to a row in the subscription matrix representing this
1038c2ecf20Sopenharmony_ci *		entry
1048c2ecf20Sopenharmony_ci */
1058c2ecf20Sopenharmony_cistruct smsm_entry {
1068c2ecf20Sopenharmony_ci	struct qcom_smsm *smsm;
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci	struct irq_domain *domain;
1098c2ecf20Sopenharmony_ci	DECLARE_BITMAP(irq_enabled, 32);
1108c2ecf20Sopenharmony_ci	DECLARE_BITMAP(irq_rising, 32);
1118c2ecf20Sopenharmony_ci	DECLARE_BITMAP(irq_falling, 32);
1128c2ecf20Sopenharmony_ci	unsigned long last_value;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	u32 *remote_state;
1158c2ecf20Sopenharmony_ci	u32 *subscription;
1168c2ecf20Sopenharmony_ci};
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci/**
1198c2ecf20Sopenharmony_ci * struct smsm_host - representation of a remote host
1208c2ecf20Sopenharmony_ci * @ipc_regmap:	regmap for outgoing interrupt
1218c2ecf20Sopenharmony_ci * @ipc_offset:	offset in @ipc_regmap for outgoing interrupt
1228c2ecf20Sopenharmony_ci * @ipc_bit:	bit in @ipc_regmap + @ipc_offset for outgoing interrupt
1238c2ecf20Sopenharmony_ci */
1248c2ecf20Sopenharmony_cistruct smsm_host {
1258c2ecf20Sopenharmony_ci	struct regmap *ipc_regmap;
1268c2ecf20Sopenharmony_ci	int ipc_offset;
1278c2ecf20Sopenharmony_ci	int ipc_bit;
1288c2ecf20Sopenharmony_ci};
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci/**
1318c2ecf20Sopenharmony_ci * smsm_update_bits() - change bit in outgoing entry and inform subscribers
1328c2ecf20Sopenharmony_ci * @data:	smsm context pointer
1338c2ecf20Sopenharmony_ci * @offset:	bit in the entry
1348c2ecf20Sopenharmony_ci * @value:	new value
1358c2ecf20Sopenharmony_ci *
1368c2ecf20Sopenharmony_ci * Used to set and clear the bits in the outgoing/local entry and inform
1378c2ecf20Sopenharmony_ci * subscribers about the change.
1388c2ecf20Sopenharmony_ci */
1398c2ecf20Sopenharmony_cistatic int smsm_update_bits(void *data, u32 mask, u32 value)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	struct qcom_smsm *smsm = data;
1428c2ecf20Sopenharmony_ci	struct smsm_host *hostp;
1438c2ecf20Sopenharmony_ci	unsigned long flags;
1448c2ecf20Sopenharmony_ci	u32 changes;
1458c2ecf20Sopenharmony_ci	u32 host;
1468c2ecf20Sopenharmony_ci	u32 orig;
1478c2ecf20Sopenharmony_ci	u32 val;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	spin_lock_irqsave(&smsm->lock, flags);
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	/* Update the entry */
1528c2ecf20Sopenharmony_ci	val = orig = readl(smsm->local_state);
1538c2ecf20Sopenharmony_ci	val &= ~mask;
1548c2ecf20Sopenharmony_ci	val |= value;
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	/* Don't signal if we didn't change the value */
1578c2ecf20Sopenharmony_ci	changes = val ^ orig;
1588c2ecf20Sopenharmony_ci	if (!changes) {
1598c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&smsm->lock, flags);
1608c2ecf20Sopenharmony_ci		goto done;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	/* Write out the new value */
1648c2ecf20Sopenharmony_ci	writel(val, smsm->local_state);
1658c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&smsm->lock, flags);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	/* Make sure the value update is ordered before any kicks */
1688c2ecf20Sopenharmony_ci	wmb();
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	/* Iterate over all hosts to check whom wants a kick */
1718c2ecf20Sopenharmony_ci	for (host = 0; host < smsm->num_hosts; host++) {
1728c2ecf20Sopenharmony_ci		hostp = &smsm->hosts[host];
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		val = readl(smsm->subscription + host);
1758c2ecf20Sopenharmony_ci		if (val & changes && hostp->ipc_regmap) {
1768c2ecf20Sopenharmony_ci			regmap_write(hostp->ipc_regmap,
1778c2ecf20Sopenharmony_ci				     hostp->ipc_offset,
1788c2ecf20Sopenharmony_ci				     BIT(hostp->ipc_bit));
1798c2ecf20Sopenharmony_ci		}
1808c2ecf20Sopenharmony_ci	}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cidone:
1838c2ecf20Sopenharmony_ci	return 0;
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic const struct qcom_smem_state_ops smsm_state_ops = {
1878c2ecf20Sopenharmony_ci	.update_bits = smsm_update_bits,
1888c2ecf20Sopenharmony_ci};
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci/**
1918c2ecf20Sopenharmony_ci * smsm_intr() - cascading IRQ handler for SMSM
1928c2ecf20Sopenharmony_ci * @irq:	unused
1938c2ecf20Sopenharmony_ci * @data:	entry related to this IRQ
1948c2ecf20Sopenharmony_ci *
1958c2ecf20Sopenharmony_ci * This function cascades an incoming interrupt from a remote system, based on
1968c2ecf20Sopenharmony_ci * the state bits and configuration.
1978c2ecf20Sopenharmony_ci */
1988c2ecf20Sopenharmony_cistatic irqreturn_t smsm_intr(int irq, void *data)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct smsm_entry *entry = data;
2018c2ecf20Sopenharmony_ci	unsigned i;
2028c2ecf20Sopenharmony_ci	int irq_pin;
2038c2ecf20Sopenharmony_ci	u32 changed;
2048c2ecf20Sopenharmony_ci	u32 val;
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci	val = readl(entry->remote_state);
2078c2ecf20Sopenharmony_ci	changed = val ^ xchg(&entry->last_value, val);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_ci	for_each_set_bit(i, entry->irq_enabled, 32) {
2108c2ecf20Sopenharmony_ci		if (!(changed & BIT(i)))
2118c2ecf20Sopenharmony_ci			continue;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci		if (val & BIT(i)) {
2148c2ecf20Sopenharmony_ci			if (test_bit(i, entry->irq_rising)) {
2158c2ecf20Sopenharmony_ci				irq_pin = irq_find_mapping(entry->domain, i);
2168c2ecf20Sopenharmony_ci				handle_nested_irq(irq_pin);
2178c2ecf20Sopenharmony_ci			}
2188c2ecf20Sopenharmony_ci		} else {
2198c2ecf20Sopenharmony_ci			if (test_bit(i, entry->irq_falling)) {
2208c2ecf20Sopenharmony_ci				irq_pin = irq_find_mapping(entry->domain, i);
2218c2ecf20Sopenharmony_ci				handle_nested_irq(irq_pin);
2228c2ecf20Sopenharmony_ci			}
2238c2ecf20Sopenharmony_ci		}
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
2278c2ecf20Sopenharmony_ci}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci/**
2308c2ecf20Sopenharmony_ci * smsm_mask_irq() - un-subscribe from cascades of IRQs of a certain staus bit
2318c2ecf20Sopenharmony_ci * @irqd:	IRQ handle to be masked
2328c2ecf20Sopenharmony_ci *
2338c2ecf20Sopenharmony_ci * This un-subscribes the local CPU from interrupts upon changes to the defines
2348c2ecf20Sopenharmony_ci * status bit. The bit is also cleared from cascading.
2358c2ecf20Sopenharmony_ci */
2368c2ecf20Sopenharmony_cistatic void smsm_mask_irq(struct irq_data *irqd)
2378c2ecf20Sopenharmony_ci{
2388c2ecf20Sopenharmony_ci	struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
2398c2ecf20Sopenharmony_ci	irq_hw_number_t irq = irqd_to_hwirq(irqd);
2408c2ecf20Sopenharmony_ci	struct qcom_smsm *smsm = entry->smsm;
2418c2ecf20Sopenharmony_ci	u32 val;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	if (entry->subscription) {
2448c2ecf20Sopenharmony_ci		val = readl(entry->subscription + smsm->local_host);
2458c2ecf20Sopenharmony_ci		val &= ~BIT(irq);
2468c2ecf20Sopenharmony_ci		writel(val, entry->subscription + smsm->local_host);
2478c2ecf20Sopenharmony_ci	}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	clear_bit(irq, entry->irq_enabled);
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci/**
2538c2ecf20Sopenharmony_ci * smsm_unmask_irq() - subscribe to cascades of IRQs of a certain status bit
2548c2ecf20Sopenharmony_ci * @irqd:	IRQ handle to be unmasked
2558c2ecf20Sopenharmony_ci *
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci * This subscribes the local CPU to interrupts upon changes to the defined
2588c2ecf20Sopenharmony_ci * status bit. The bit is also marked for cascading.
2598c2ecf20Sopenharmony_ci
2608c2ecf20Sopenharmony_ci */
2618c2ecf20Sopenharmony_cistatic void smsm_unmask_irq(struct irq_data *irqd)
2628c2ecf20Sopenharmony_ci{
2638c2ecf20Sopenharmony_ci	struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
2648c2ecf20Sopenharmony_ci	irq_hw_number_t irq = irqd_to_hwirq(irqd);
2658c2ecf20Sopenharmony_ci	struct qcom_smsm *smsm = entry->smsm;
2668c2ecf20Sopenharmony_ci	u32 val;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	/* Make sure our last cached state is up-to-date */
2698c2ecf20Sopenharmony_ci	if (readl(entry->remote_state) & BIT(irq))
2708c2ecf20Sopenharmony_ci		set_bit(irq, &entry->last_value);
2718c2ecf20Sopenharmony_ci	else
2728c2ecf20Sopenharmony_ci		clear_bit(irq, &entry->last_value);
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	set_bit(irq, entry->irq_enabled);
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	if (entry->subscription) {
2778c2ecf20Sopenharmony_ci		val = readl(entry->subscription + smsm->local_host);
2788c2ecf20Sopenharmony_ci		val |= BIT(irq);
2798c2ecf20Sopenharmony_ci		writel(val, entry->subscription + smsm->local_host);
2808c2ecf20Sopenharmony_ci	}
2818c2ecf20Sopenharmony_ci}
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci/**
2848c2ecf20Sopenharmony_ci * smsm_set_irq_type() - updates the requested IRQ type for the cascading
2858c2ecf20Sopenharmony_ci * @irqd:	consumer interrupt handle
2868c2ecf20Sopenharmony_ci * @type:	requested flags
2878c2ecf20Sopenharmony_ci */
2888c2ecf20Sopenharmony_cistatic int smsm_set_irq_type(struct irq_data *irqd, unsigned int type)
2898c2ecf20Sopenharmony_ci{
2908c2ecf20Sopenharmony_ci	struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
2918c2ecf20Sopenharmony_ci	irq_hw_number_t irq = irqd_to_hwirq(irqd);
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	if (!(type & IRQ_TYPE_EDGE_BOTH))
2948c2ecf20Sopenharmony_ci		return -EINVAL;
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_ci	if (type & IRQ_TYPE_EDGE_RISING)
2978c2ecf20Sopenharmony_ci		set_bit(irq, entry->irq_rising);
2988c2ecf20Sopenharmony_ci	else
2998c2ecf20Sopenharmony_ci		clear_bit(irq, entry->irq_rising);
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	if (type & IRQ_TYPE_EDGE_FALLING)
3028c2ecf20Sopenharmony_ci		set_bit(irq, entry->irq_falling);
3038c2ecf20Sopenharmony_ci	else
3048c2ecf20Sopenharmony_ci		clear_bit(irq, entry->irq_falling);
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	return 0;
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_cistatic struct irq_chip smsm_irq_chip = {
3108c2ecf20Sopenharmony_ci	.name           = "smsm",
3118c2ecf20Sopenharmony_ci	.irq_mask       = smsm_mask_irq,
3128c2ecf20Sopenharmony_ci	.irq_unmask     = smsm_unmask_irq,
3138c2ecf20Sopenharmony_ci	.irq_set_type	= smsm_set_irq_type,
3148c2ecf20Sopenharmony_ci};
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci/**
3178c2ecf20Sopenharmony_ci * smsm_irq_map() - sets up a mapping for a cascaded IRQ
3188c2ecf20Sopenharmony_ci * @d:		IRQ domain representing an entry
3198c2ecf20Sopenharmony_ci * @irq:	IRQ to set up
3208c2ecf20Sopenharmony_ci * @hw:		unused
3218c2ecf20Sopenharmony_ci */
3228c2ecf20Sopenharmony_cistatic int smsm_irq_map(struct irq_domain *d,
3238c2ecf20Sopenharmony_ci			unsigned int irq,
3248c2ecf20Sopenharmony_ci			irq_hw_number_t hw)
3258c2ecf20Sopenharmony_ci{
3268c2ecf20Sopenharmony_ci	struct smsm_entry *entry = d->host_data;
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	irq_set_chip_and_handler(irq, &smsm_irq_chip, handle_level_irq);
3298c2ecf20Sopenharmony_ci	irq_set_chip_data(irq, entry);
3308c2ecf20Sopenharmony_ci	irq_set_nested_thread(irq, 1);
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	return 0;
3338c2ecf20Sopenharmony_ci}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cistatic const struct irq_domain_ops smsm_irq_ops = {
3368c2ecf20Sopenharmony_ci	.map = smsm_irq_map,
3378c2ecf20Sopenharmony_ci	.xlate = irq_domain_xlate_twocell,
3388c2ecf20Sopenharmony_ci};
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci/**
3418c2ecf20Sopenharmony_ci * smsm_parse_ipc() - parses a qcom,ipc-%d device tree property
3428c2ecf20Sopenharmony_ci * @smsm:	smsm driver context
3438c2ecf20Sopenharmony_ci * @host_id:	index of the remote host to be resolved
3448c2ecf20Sopenharmony_ci *
3458c2ecf20Sopenharmony_ci * Parses device tree to acquire the information needed for sending the
3468c2ecf20Sopenharmony_ci * outgoing interrupts to a remote host - identified by @host_id.
3478c2ecf20Sopenharmony_ci */
3488c2ecf20Sopenharmony_cistatic int smsm_parse_ipc(struct qcom_smsm *smsm, unsigned host_id)
3498c2ecf20Sopenharmony_ci{
3508c2ecf20Sopenharmony_ci	struct device_node *syscon;
3518c2ecf20Sopenharmony_ci	struct device_node *node = smsm->dev->of_node;
3528c2ecf20Sopenharmony_ci	struct smsm_host *host = &smsm->hosts[host_id];
3538c2ecf20Sopenharmony_ci	char key[16];
3548c2ecf20Sopenharmony_ci	int ret;
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_ci	snprintf(key, sizeof(key), "qcom,ipc-%d", host_id);
3578c2ecf20Sopenharmony_ci	syscon = of_parse_phandle(node, key, 0);
3588c2ecf20Sopenharmony_ci	if (!syscon)
3598c2ecf20Sopenharmony_ci		return 0;
3608c2ecf20Sopenharmony_ci
3618c2ecf20Sopenharmony_ci	host->ipc_regmap = syscon_node_to_regmap(syscon);
3628c2ecf20Sopenharmony_ci	of_node_put(syscon);
3638c2ecf20Sopenharmony_ci	if (IS_ERR(host->ipc_regmap))
3648c2ecf20Sopenharmony_ci		return PTR_ERR(host->ipc_regmap);
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	ret = of_property_read_u32_index(node, key, 1, &host->ipc_offset);
3678c2ecf20Sopenharmony_ci	if (ret < 0) {
3688c2ecf20Sopenharmony_ci		dev_err(smsm->dev, "no offset in %s\n", key);
3698c2ecf20Sopenharmony_ci		return -EINVAL;
3708c2ecf20Sopenharmony_ci	}
3718c2ecf20Sopenharmony_ci
3728c2ecf20Sopenharmony_ci	ret = of_property_read_u32_index(node, key, 2, &host->ipc_bit);
3738c2ecf20Sopenharmony_ci	if (ret < 0) {
3748c2ecf20Sopenharmony_ci		dev_err(smsm->dev, "no bit in %s\n", key);
3758c2ecf20Sopenharmony_ci		return -EINVAL;
3768c2ecf20Sopenharmony_ci	}
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	return 0;
3798c2ecf20Sopenharmony_ci}
3808c2ecf20Sopenharmony_ci
3818c2ecf20Sopenharmony_ci/**
3828c2ecf20Sopenharmony_ci * smsm_inbound_entry() - parse DT and set up an entry representing a remote system
3838c2ecf20Sopenharmony_ci * @smsm:	smsm driver context
3848c2ecf20Sopenharmony_ci * @entry:	entry context to be set up
3858c2ecf20Sopenharmony_ci * @node:	dt node containing the entry's properties
3868c2ecf20Sopenharmony_ci */
3878c2ecf20Sopenharmony_cistatic int smsm_inbound_entry(struct qcom_smsm *smsm,
3888c2ecf20Sopenharmony_ci			      struct smsm_entry *entry,
3898c2ecf20Sopenharmony_ci			      struct device_node *node)
3908c2ecf20Sopenharmony_ci{
3918c2ecf20Sopenharmony_ci	int ret;
3928c2ecf20Sopenharmony_ci	int irq;
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	irq = irq_of_parse_and_map(node, 0);
3958c2ecf20Sopenharmony_ci	if (!irq) {
3968c2ecf20Sopenharmony_ci		dev_err(smsm->dev, "failed to parse smsm interrupt\n");
3978c2ecf20Sopenharmony_ci		return -EINVAL;
3988c2ecf20Sopenharmony_ci	}
3998c2ecf20Sopenharmony_ci
4008c2ecf20Sopenharmony_ci	ret = devm_request_threaded_irq(smsm->dev, irq,
4018c2ecf20Sopenharmony_ci					NULL, smsm_intr,
4028c2ecf20Sopenharmony_ci					IRQF_ONESHOT,
4038c2ecf20Sopenharmony_ci					"smsm", (void *)entry);
4048c2ecf20Sopenharmony_ci	if (ret) {
4058c2ecf20Sopenharmony_ci		dev_err(smsm->dev, "failed to request interrupt\n");
4068c2ecf20Sopenharmony_ci		return ret;
4078c2ecf20Sopenharmony_ci	}
4088c2ecf20Sopenharmony_ci
4098c2ecf20Sopenharmony_ci	entry->domain = irq_domain_add_linear(node, 32, &smsm_irq_ops, entry);
4108c2ecf20Sopenharmony_ci	if (!entry->domain) {
4118c2ecf20Sopenharmony_ci		dev_err(smsm->dev, "failed to add irq_domain\n");
4128c2ecf20Sopenharmony_ci		return -ENOMEM;
4138c2ecf20Sopenharmony_ci	}
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_ci	return 0;
4168c2ecf20Sopenharmony_ci}
4178c2ecf20Sopenharmony_ci
4188c2ecf20Sopenharmony_ci/**
4198c2ecf20Sopenharmony_ci * smsm_get_size_info() - parse the optional memory segment for sizes
4208c2ecf20Sopenharmony_ci * @smsm:	smsm driver context
4218c2ecf20Sopenharmony_ci *
4228c2ecf20Sopenharmony_ci * Attempt to acquire the number of hosts and entries from the optional shared
4238c2ecf20Sopenharmony_ci * memory location. Not being able to find this segment should indicate that
4248c2ecf20Sopenharmony_ci * we're on a older system where these values was hard coded to
4258c2ecf20Sopenharmony_ci * SMSM_DEFAULT_NUM_ENTRIES and SMSM_DEFAULT_NUM_HOSTS.
4268c2ecf20Sopenharmony_ci *
4278c2ecf20Sopenharmony_ci * Returns 0 on success, negative errno on failure.
4288c2ecf20Sopenharmony_ci */
4298c2ecf20Sopenharmony_cistatic int smsm_get_size_info(struct qcom_smsm *smsm)
4308c2ecf20Sopenharmony_ci{
4318c2ecf20Sopenharmony_ci	size_t size;
4328c2ecf20Sopenharmony_ci	struct {
4338c2ecf20Sopenharmony_ci		u32 num_hosts;
4348c2ecf20Sopenharmony_ci		u32 num_entries;
4358c2ecf20Sopenharmony_ci		u32 reserved0;
4368c2ecf20Sopenharmony_ci		u32 reserved1;
4378c2ecf20Sopenharmony_ci	} *info;
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SIZE_INFO, &size);
4408c2ecf20Sopenharmony_ci	if (IS_ERR(info) && PTR_ERR(info) != -ENOENT) {
4418c2ecf20Sopenharmony_ci		if (PTR_ERR(info) != -EPROBE_DEFER)
4428c2ecf20Sopenharmony_ci			dev_err(smsm->dev, "unable to retrieve smsm size info\n");
4438c2ecf20Sopenharmony_ci		return PTR_ERR(info);
4448c2ecf20Sopenharmony_ci	} else if (IS_ERR(info) || size != sizeof(*info)) {
4458c2ecf20Sopenharmony_ci		dev_warn(smsm->dev, "no smsm size info, using defaults\n");
4468c2ecf20Sopenharmony_ci		smsm->num_entries = SMSM_DEFAULT_NUM_ENTRIES;
4478c2ecf20Sopenharmony_ci		smsm->num_hosts = SMSM_DEFAULT_NUM_HOSTS;
4488c2ecf20Sopenharmony_ci		return 0;
4498c2ecf20Sopenharmony_ci	}
4508c2ecf20Sopenharmony_ci
4518c2ecf20Sopenharmony_ci	smsm->num_entries = info->num_entries;
4528c2ecf20Sopenharmony_ci	smsm->num_hosts = info->num_hosts;
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	dev_dbg(smsm->dev,
4558c2ecf20Sopenharmony_ci		"found custom size of smsm: %d entries %d hosts\n",
4568c2ecf20Sopenharmony_ci		smsm->num_entries, smsm->num_hosts);
4578c2ecf20Sopenharmony_ci
4588c2ecf20Sopenharmony_ci	return 0;
4598c2ecf20Sopenharmony_ci}
4608c2ecf20Sopenharmony_ci
4618c2ecf20Sopenharmony_cistatic int qcom_smsm_probe(struct platform_device *pdev)
4628c2ecf20Sopenharmony_ci{
4638c2ecf20Sopenharmony_ci	struct device_node *local_node;
4648c2ecf20Sopenharmony_ci	struct device_node *node;
4658c2ecf20Sopenharmony_ci	struct smsm_entry *entry;
4668c2ecf20Sopenharmony_ci	struct qcom_smsm *smsm;
4678c2ecf20Sopenharmony_ci	u32 *intr_mask;
4688c2ecf20Sopenharmony_ci	size_t size;
4698c2ecf20Sopenharmony_ci	u32 *states;
4708c2ecf20Sopenharmony_ci	u32 id;
4718c2ecf20Sopenharmony_ci	int ret;
4728c2ecf20Sopenharmony_ci
4738c2ecf20Sopenharmony_ci	smsm = devm_kzalloc(&pdev->dev, sizeof(*smsm), GFP_KERNEL);
4748c2ecf20Sopenharmony_ci	if (!smsm)
4758c2ecf20Sopenharmony_ci		return -ENOMEM;
4768c2ecf20Sopenharmony_ci	smsm->dev = &pdev->dev;
4778c2ecf20Sopenharmony_ci	spin_lock_init(&smsm->lock);
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	ret = smsm_get_size_info(smsm);
4808c2ecf20Sopenharmony_ci	if (ret)
4818c2ecf20Sopenharmony_ci		return ret;
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	smsm->entries = devm_kcalloc(&pdev->dev,
4848c2ecf20Sopenharmony_ci				     smsm->num_entries,
4858c2ecf20Sopenharmony_ci				     sizeof(struct smsm_entry),
4868c2ecf20Sopenharmony_ci				     GFP_KERNEL);
4878c2ecf20Sopenharmony_ci	if (!smsm->entries)
4888c2ecf20Sopenharmony_ci		return -ENOMEM;
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci	smsm->hosts = devm_kcalloc(&pdev->dev,
4918c2ecf20Sopenharmony_ci				   smsm->num_hosts,
4928c2ecf20Sopenharmony_ci				   sizeof(struct smsm_host),
4938c2ecf20Sopenharmony_ci				   GFP_KERNEL);
4948c2ecf20Sopenharmony_ci	if (!smsm->hosts)
4958c2ecf20Sopenharmony_ci		return -ENOMEM;
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci	for_each_child_of_node(pdev->dev.of_node, local_node) {
4988c2ecf20Sopenharmony_ci		if (of_find_property(local_node, "#qcom,smem-state-cells", NULL))
4998c2ecf20Sopenharmony_ci			break;
5008c2ecf20Sopenharmony_ci	}
5018c2ecf20Sopenharmony_ci	if (!local_node) {
5028c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "no state entry\n");
5038c2ecf20Sopenharmony_ci		return -EINVAL;
5048c2ecf20Sopenharmony_ci	}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_ci	of_property_read_u32(pdev->dev.of_node,
5078c2ecf20Sopenharmony_ci			     "qcom,local-host",
5088c2ecf20Sopenharmony_ci			     &smsm->local_host);
5098c2ecf20Sopenharmony_ci
5108c2ecf20Sopenharmony_ci	/* Parse the host properties */
5118c2ecf20Sopenharmony_ci	for (id = 0; id < smsm->num_hosts; id++) {
5128c2ecf20Sopenharmony_ci		ret = smsm_parse_ipc(smsm, id);
5138c2ecf20Sopenharmony_ci		if (ret < 0)
5148c2ecf20Sopenharmony_ci			goto out_put;
5158c2ecf20Sopenharmony_ci	}
5168c2ecf20Sopenharmony_ci
5178c2ecf20Sopenharmony_ci	/* Acquire the main SMSM state vector */
5188c2ecf20Sopenharmony_ci	ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE,
5198c2ecf20Sopenharmony_ci			      smsm->num_entries * sizeof(u32));
5208c2ecf20Sopenharmony_ci	if (ret < 0 && ret != -EEXIST) {
5218c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "unable to allocate shared state entry\n");
5228c2ecf20Sopenharmony_ci		goto out_put;
5238c2ecf20Sopenharmony_ci	}
5248c2ecf20Sopenharmony_ci
5258c2ecf20Sopenharmony_ci	states = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE, NULL);
5268c2ecf20Sopenharmony_ci	if (IS_ERR(states)) {
5278c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Unable to acquire shared state entry\n");
5288c2ecf20Sopenharmony_ci		ret = PTR_ERR(states);
5298c2ecf20Sopenharmony_ci		goto out_put;
5308c2ecf20Sopenharmony_ci	}
5318c2ecf20Sopenharmony_ci
5328c2ecf20Sopenharmony_ci	/* Acquire the list of interrupt mask vectors */
5338c2ecf20Sopenharmony_ci	size = smsm->num_entries * smsm->num_hosts * sizeof(u32);
5348c2ecf20Sopenharmony_ci	ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, size);
5358c2ecf20Sopenharmony_ci	if (ret < 0 && ret != -EEXIST) {
5368c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "unable to allocate smsm interrupt mask\n");
5378c2ecf20Sopenharmony_ci		goto out_put;
5388c2ecf20Sopenharmony_ci	}
5398c2ecf20Sopenharmony_ci
5408c2ecf20Sopenharmony_ci	intr_mask = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, NULL);
5418c2ecf20Sopenharmony_ci	if (IS_ERR(intr_mask)) {
5428c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "unable to acquire shared memory interrupt mask\n");
5438c2ecf20Sopenharmony_ci		ret = PTR_ERR(intr_mask);
5448c2ecf20Sopenharmony_ci		goto out_put;
5458c2ecf20Sopenharmony_ci	}
5468c2ecf20Sopenharmony_ci
5478c2ecf20Sopenharmony_ci	/* Setup the reference to the local state bits */
5488c2ecf20Sopenharmony_ci	smsm->local_state = states + smsm->local_host;
5498c2ecf20Sopenharmony_ci	smsm->subscription = intr_mask + smsm->local_host * smsm->num_hosts;
5508c2ecf20Sopenharmony_ci
5518c2ecf20Sopenharmony_ci	/* Register the outgoing state */
5528c2ecf20Sopenharmony_ci	smsm->state = qcom_smem_state_register(local_node, &smsm_state_ops, smsm);
5538c2ecf20Sopenharmony_ci	if (IS_ERR(smsm->state)) {
5548c2ecf20Sopenharmony_ci		dev_err(smsm->dev, "failed to register qcom_smem_state\n");
5558c2ecf20Sopenharmony_ci		ret = PTR_ERR(smsm->state);
5568c2ecf20Sopenharmony_ci		goto out_put;
5578c2ecf20Sopenharmony_ci	}
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_ci	/* Register handlers for remote processor entries of interest. */
5608c2ecf20Sopenharmony_ci	for_each_available_child_of_node(pdev->dev.of_node, node) {
5618c2ecf20Sopenharmony_ci		if (!of_property_read_bool(node, "interrupt-controller"))
5628c2ecf20Sopenharmony_ci			continue;
5638c2ecf20Sopenharmony_ci
5648c2ecf20Sopenharmony_ci		ret = of_property_read_u32(node, "reg", &id);
5658c2ecf20Sopenharmony_ci		if (ret || id >= smsm->num_entries) {
5668c2ecf20Sopenharmony_ci			dev_err(&pdev->dev, "invalid reg of entry\n");
5678c2ecf20Sopenharmony_ci			if (!ret)
5688c2ecf20Sopenharmony_ci				ret = -EINVAL;
5698c2ecf20Sopenharmony_ci			goto unwind_interfaces;
5708c2ecf20Sopenharmony_ci		}
5718c2ecf20Sopenharmony_ci		entry = &smsm->entries[id];
5728c2ecf20Sopenharmony_ci
5738c2ecf20Sopenharmony_ci		entry->smsm = smsm;
5748c2ecf20Sopenharmony_ci		entry->remote_state = states + id;
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ci		/* Setup subscription pointers and unsubscribe to any kicks */
5778c2ecf20Sopenharmony_ci		entry->subscription = intr_mask + id * smsm->num_hosts;
5788c2ecf20Sopenharmony_ci		writel(0, entry->subscription + smsm->local_host);
5798c2ecf20Sopenharmony_ci
5808c2ecf20Sopenharmony_ci		ret = smsm_inbound_entry(smsm, entry, node);
5818c2ecf20Sopenharmony_ci		if (ret < 0)
5828c2ecf20Sopenharmony_ci			goto unwind_interfaces;
5838c2ecf20Sopenharmony_ci	}
5848c2ecf20Sopenharmony_ci
5858c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, smsm);
5868c2ecf20Sopenharmony_ci	of_node_put(local_node);
5878c2ecf20Sopenharmony_ci
5888c2ecf20Sopenharmony_ci	return 0;
5898c2ecf20Sopenharmony_ci
5908c2ecf20Sopenharmony_ciunwind_interfaces:
5918c2ecf20Sopenharmony_ci	of_node_put(node);
5928c2ecf20Sopenharmony_ci	for (id = 0; id < smsm->num_entries; id++)
5938c2ecf20Sopenharmony_ci		if (smsm->entries[id].domain)
5948c2ecf20Sopenharmony_ci			irq_domain_remove(smsm->entries[id].domain);
5958c2ecf20Sopenharmony_ci
5968c2ecf20Sopenharmony_ci	qcom_smem_state_unregister(smsm->state);
5978c2ecf20Sopenharmony_ciout_put:
5988c2ecf20Sopenharmony_ci	of_node_put(local_node);
5998c2ecf20Sopenharmony_ci	return ret;
6008c2ecf20Sopenharmony_ci}
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_cistatic int qcom_smsm_remove(struct platform_device *pdev)
6038c2ecf20Sopenharmony_ci{
6048c2ecf20Sopenharmony_ci	struct qcom_smsm *smsm = platform_get_drvdata(pdev);
6058c2ecf20Sopenharmony_ci	unsigned id;
6068c2ecf20Sopenharmony_ci
6078c2ecf20Sopenharmony_ci	for (id = 0; id < smsm->num_entries; id++)
6088c2ecf20Sopenharmony_ci		if (smsm->entries[id].domain)
6098c2ecf20Sopenharmony_ci			irq_domain_remove(smsm->entries[id].domain);
6108c2ecf20Sopenharmony_ci
6118c2ecf20Sopenharmony_ci	qcom_smem_state_unregister(smsm->state);
6128c2ecf20Sopenharmony_ci
6138c2ecf20Sopenharmony_ci	return 0;
6148c2ecf20Sopenharmony_ci}
6158c2ecf20Sopenharmony_ci
6168c2ecf20Sopenharmony_cistatic const struct of_device_id qcom_smsm_of_match[] = {
6178c2ecf20Sopenharmony_ci	{ .compatible = "qcom,smsm" },
6188c2ecf20Sopenharmony_ci	{}
6198c2ecf20Sopenharmony_ci};
6208c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, qcom_smsm_of_match);
6218c2ecf20Sopenharmony_ci
6228c2ecf20Sopenharmony_cistatic struct platform_driver qcom_smsm_driver = {
6238c2ecf20Sopenharmony_ci	.probe = qcom_smsm_probe,
6248c2ecf20Sopenharmony_ci	.remove = qcom_smsm_remove,
6258c2ecf20Sopenharmony_ci	.driver  = {
6268c2ecf20Sopenharmony_ci		.name  = "qcom-smsm",
6278c2ecf20Sopenharmony_ci		.of_match_table = qcom_smsm_of_match,
6288c2ecf20Sopenharmony_ci	},
6298c2ecf20Sopenharmony_ci};
6308c2ecf20Sopenharmony_cimodule_platform_driver(qcom_smsm_driver);
6318c2ecf20Sopenharmony_ci
6328c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm Shared Memory State Machine driver");
6338c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
634