197fb5e8dSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 2c97c4090SBjorn Andersson /* 3c97c4090SBjorn Andersson * Copyright (c) 2015, Sony Mobile Communications Inc. 4c97c4090SBjorn Andersson * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. 5c97c4090SBjorn Andersson */ 6c97c4090SBjorn Andersson 7c97c4090SBjorn Andersson #include <linux/interrupt.h> 8c97c4090SBjorn Andersson #include <linux/mfd/syscon.h> 9c97c4090SBjorn Andersson #include <linux/module.h> 10c97c4090SBjorn Andersson #include <linux/of_irq.h> 11c97c4090SBjorn Andersson #include <linux/platform_device.h> 12c97c4090SBjorn Andersson #include <linux/spinlock.h> 13c97c4090SBjorn Andersson #include <linux/regmap.h> 14c97c4090SBjorn Andersson #include <linux/soc/qcom/smem.h> 15c97c4090SBjorn Andersson #include <linux/soc/qcom/smem_state.h> 16c97c4090SBjorn Andersson 17c97c4090SBjorn Andersson /* 18c97c4090SBjorn Andersson * This driver implements the Qualcomm Shared Memory State Machine, a mechanism 19c97c4090SBjorn Andersson * for communicating single bit state information to remote processors. 20c97c4090SBjorn Andersson * 21c97c4090SBjorn Andersson * The implementation is based on two sections of shared memory; the first 22c97c4090SBjorn Andersson * holding the state bits and the second holding a matrix of subscription bits. 23c97c4090SBjorn Andersson * 24c97c4090SBjorn Andersson * The state bits are structured in entries of 32 bits, each belonging to one 25c97c4090SBjorn Andersson * system in the SoC. The entry belonging to the local system is considered 26c97c4090SBjorn Andersson * read-write, while the rest should be considered read-only. 27c97c4090SBjorn Andersson * 28c97c4090SBjorn Andersson * The subscription matrix consists of N bitmaps per entry, denoting interest 29c97c4090SBjorn Andersson * in updates of the entry for each of the N hosts. Upon updating a state bit 30c97c4090SBjorn Andersson * each host's subscription bitmap should be queried and the remote system 31c97c4090SBjorn Andersson * should be interrupted if they request so. 32c97c4090SBjorn Andersson * 33c97c4090SBjorn Andersson * The subscription matrix is laid out in entry-major order: 34c97c4090SBjorn Andersson * entry0: [host0 ... hostN] 35c97c4090SBjorn Andersson * . 36c97c4090SBjorn Andersson * . 37c97c4090SBjorn Andersson * entryM: [host0 ... hostN] 38c97c4090SBjorn Andersson * 39c97c4090SBjorn Andersson * A third, optional, shared memory region might contain information regarding 40c97c4090SBjorn Andersson * the number of entries in the state bitmap as well as number of columns in 41c97c4090SBjorn Andersson * the subscription matrix. 42c97c4090SBjorn Andersson */ 43c97c4090SBjorn Andersson 44c97c4090SBjorn Andersson /* 45c97c4090SBjorn Andersson * Shared memory identifiers, used to acquire handles to respective memory 46c97c4090SBjorn Andersson * region. 47c97c4090SBjorn Andersson */ 48c97c4090SBjorn Andersson #define SMEM_SMSM_SHARED_STATE 85 49c97c4090SBjorn Andersson #define SMEM_SMSM_CPU_INTR_MASK 333 50c97c4090SBjorn Andersson #define SMEM_SMSM_SIZE_INFO 419 51c97c4090SBjorn Andersson 52c97c4090SBjorn Andersson /* 53c97c4090SBjorn Andersson * Default sizes, in case SMEM_SMSM_SIZE_INFO is not found. 54c97c4090SBjorn Andersson */ 55c97c4090SBjorn Andersson #define SMSM_DEFAULT_NUM_ENTRIES 8 56c97c4090SBjorn Andersson #define SMSM_DEFAULT_NUM_HOSTS 3 57c97c4090SBjorn Andersson 58c97c4090SBjorn Andersson struct smsm_entry; 59c97c4090SBjorn Andersson struct smsm_host; 60c97c4090SBjorn Andersson 61c97c4090SBjorn Andersson /** 62c97c4090SBjorn Andersson * struct qcom_smsm - smsm driver context 63c97c4090SBjorn Andersson * @dev: smsm device pointer 64c97c4090SBjorn Andersson * @local_host: column in the subscription matrix representing this system 65c97c4090SBjorn Andersson * @num_hosts: number of columns in the subscription matrix 66c97c4090SBjorn Andersson * @num_entries: number of entries in the state map and rows in the subscription 67c97c4090SBjorn Andersson * matrix 68c97c4090SBjorn Andersson * @local_state: pointer to the local processor's state bits 69c97c4090SBjorn Andersson * @subscription: pointer to local processor's row in subscription matrix 70c97c4090SBjorn Andersson * @state: smem state handle 71c97c4090SBjorn Andersson * @lock: spinlock for read-modify-write of the outgoing state 72c97c4090SBjorn Andersson * @entries: context for each of the entries 73c97c4090SBjorn Andersson * @hosts: context for each of the hosts 74c97c4090SBjorn Andersson */ 75c97c4090SBjorn Andersson struct qcom_smsm { 76c97c4090SBjorn Andersson struct device *dev; 77c97c4090SBjorn Andersson 78c97c4090SBjorn Andersson u32 local_host; 79c97c4090SBjorn Andersson 80c97c4090SBjorn Andersson u32 num_hosts; 81c97c4090SBjorn Andersson u32 num_entries; 82c97c4090SBjorn Andersson 83c97c4090SBjorn Andersson u32 *local_state; 84c97c4090SBjorn Andersson u32 *subscription; 85c97c4090SBjorn Andersson struct qcom_smem_state *state; 86c97c4090SBjorn Andersson 87c97c4090SBjorn Andersson spinlock_t lock; 88c97c4090SBjorn Andersson 89c97c4090SBjorn Andersson struct smsm_entry *entries; 90c97c4090SBjorn Andersson struct smsm_host *hosts; 91c97c4090SBjorn Andersson }; 92c97c4090SBjorn Andersson 93c97c4090SBjorn Andersson /** 94c97c4090SBjorn Andersson * struct smsm_entry - per remote processor entry context 95c97c4090SBjorn Andersson * @smsm: back-reference to driver context 96c97c4090SBjorn Andersson * @domain: IRQ domain for this entry, if representing a remote system 97c97c4090SBjorn Andersson * @irq_enabled: bitmap of which state bits IRQs are enabled 98c97c4090SBjorn Andersson * @irq_rising: bitmap tracking if rising bits should be propagated 99c97c4090SBjorn Andersson * @irq_falling: bitmap tracking if falling bits should be propagated 100c97c4090SBjorn Andersson * @last_value: snapshot of state bits last time the interrupts where propagated 101c97c4090SBjorn Andersson * @remote_state: pointer to this entry's state bits 102c97c4090SBjorn Andersson * @subscription: pointer to a row in the subscription matrix representing this 103c97c4090SBjorn Andersson * entry 104c97c4090SBjorn Andersson */ 105c97c4090SBjorn Andersson struct smsm_entry { 106c97c4090SBjorn Andersson struct qcom_smsm *smsm; 107c97c4090SBjorn Andersson 108c97c4090SBjorn Andersson struct irq_domain *domain; 109c97c4090SBjorn Andersson DECLARE_BITMAP(irq_enabled, 32); 110c97c4090SBjorn Andersson DECLARE_BITMAP(irq_rising, 32); 111c97c4090SBjorn Andersson DECLARE_BITMAP(irq_falling, 32); 112e3d45719SStephan Gerhold unsigned long last_value; 113c97c4090SBjorn Andersson 114c97c4090SBjorn Andersson u32 *remote_state; 115c97c4090SBjorn Andersson u32 *subscription; 116c97c4090SBjorn Andersson }; 117c97c4090SBjorn Andersson 118c97c4090SBjorn Andersson /** 119c97c4090SBjorn Andersson * struct smsm_host - representation of a remote host 120c97c4090SBjorn Andersson * @ipc_regmap: regmap for outgoing interrupt 121c97c4090SBjorn Andersson * @ipc_offset: offset in @ipc_regmap for outgoing interrupt 122c97c4090SBjorn Andersson * @ipc_bit: bit in @ipc_regmap + @ipc_offset for outgoing interrupt 123c97c4090SBjorn Andersson */ 124c97c4090SBjorn Andersson struct smsm_host { 125c97c4090SBjorn Andersson struct regmap *ipc_regmap; 126c97c4090SBjorn Andersson int ipc_offset; 127c97c4090SBjorn Andersson int ipc_bit; 128c97c4090SBjorn Andersson }; 129c97c4090SBjorn Andersson 130c97c4090SBjorn Andersson /** 131c97c4090SBjorn Andersson * smsm_update_bits() - change bit in outgoing entry and inform subscribers 132c97c4090SBjorn Andersson * @data: smsm context pointer 133fac312dfSLee Jones * @mask: value mask 134c97c4090SBjorn Andersson * @value: new value 135c97c4090SBjorn Andersson * 136c97c4090SBjorn Andersson * Used to set and clear the bits in the outgoing/local entry and inform 137c97c4090SBjorn Andersson * subscribers about the change. 138c97c4090SBjorn Andersson */ 139c97c4090SBjorn Andersson static int smsm_update_bits(void *data, u32 mask, u32 value) 140c97c4090SBjorn Andersson { 141c97c4090SBjorn Andersson struct qcom_smsm *smsm = data; 142c97c4090SBjorn Andersson struct smsm_host *hostp; 143c97c4090SBjorn Andersson unsigned long flags; 144c97c4090SBjorn Andersson u32 changes; 145c97c4090SBjorn Andersson u32 host; 146c97c4090SBjorn Andersson u32 orig; 147c97c4090SBjorn Andersson u32 val; 148c97c4090SBjorn Andersson 149c97c4090SBjorn Andersson spin_lock_irqsave(&smsm->lock, flags); 150c97c4090SBjorn Andersson 151c97c4090SBjorn Andersson /* Update the entry */ 152c97c4090SBjorn Andersson val = orig = readl(smsm->local_state); 153c97c4090SBjorn Andersson val &= ~mask; 154c97c4090SBjorn Andersson val |= value; 155c97c4090SBjorn Andersson 156c97c4090SBjorn Andersson /* Don't signal if we didn't change the value */ 157c97c4090SBjorn Andersson changes = val ^ orig; 158c97c4090SBjorn Andersson if (!changes) { 159c97c4090SBjorn Andersson spin_unlock_irqrestore(&smsm->lock, flags); 160c97c4090SBjorn Andersson goto done; 161c97c4090SBjorn Andersson } 162c97c4090SBjorn Andersson 163c97c4090SBjorn Andersson /* Write out the new value */ 164c97c4090SBjorn Andersson writel(val, smsm->local_state); 165c97c4090SBjorn Andersson spin_unlock_irqrestore(&smsm->lock, flags); 166c97c4090SBjorn Andersson 167c97c4090SBjorn Andersson /* Make sure the value update is ordered before any kicks */ 168c97c4090SBjorn Andersson wmb(); 169c97c4090SBjorn Andersson 170c97c4090SBjorn Andersson /* Iterate over all hosts to check whom wants a kick */ 171c97c4090SBjorn Andersson for (host = 0; host < smsm->num_hosts; host++) { 172c97c4090SBjorn Andersson hostp = &smsm->hosts[host]; 173c97c4090SBjorn Andersson 174c97c4090SBjorn Andersson val = readl(smsm->subscription + host); 175c97c4090SBjorn Andersson if (val & changes && hostp->ipc_regmap) { 176c97c4090SBjorn Andersson regmap_write(hostp->ipc_regmap, 177c97c4090SBjorn Andersson hostp->ipc_offset, 178c97c4090SBjorn Andersson BIT(hostp->ipc_bit)); 179c97c4090SBjorn Andersson } 180c97c4090SBjorn Andersson } 181c97c4090SBjorn Andersson 182c97c4090SBjorn Andersson done: 183c97c4090SBjorn Andersson return 0; 184c97c4090SBjorn Andersson } 185c97c4090SBjorn Andersson 186c97c4090SBjorn Andersson static const struct qcom_smem_state_ops smsm_state_ops = { 187c97c4090SBjorn Andersson .update_bits = smsm_update_bits, 188c97c4090SBjorn Andersson }; 189c97c4090SBjorn Andersson 190c97c4090SBjorn Andersson /** 191c97c4090SBjorn Andersson * smsm_intr() - cascading IRQ handler for SMSM 192c97c4090SBjorn Andersson * @irq: unused 193c97c4090SBjorn Andersson * @data: entry related to this IRQ 194c97c4090SBjorn Andersson * 195c97c4090SBjorn Andersson * This function cascades an incoming interrupt from a remote system, based on 196c97c4090SBjorn Andersson * the state bits and configuration. 197c97c4090SBjorn Andersson */ 198c97c4090SBjorn Andersson static irqreturn_t smsm_intr(int irq, void *data) 199c97c4090SBjorn Andersson { 200c97c4090SBjorn Andersson struct smsm_entry *entry = data; 201c97c4090SBjorn Andersson unsigned i; 202c97c4090SBjorn Andersson int irq_pin; 203c97c4090SBjorn Andersson u32 changed; 204c97c4090SBjorn Andersson u32 val; 205c97c4090SBjorn Andersson 206c97c4090SBjorn Andersson val = readl(entry->remote_state); 207e3d45719SStephan Gerhold changed = val ^ xchg(&entry->last_value, val); 208c97c4090SBjorn Andersson 209c97c4090SBjorn Andersson for_each_set_bit(i, entry->irq_enabled, 32) { 210c97c4090SBjorn Andersson if (!(changed & BIT(i))) 211c97c4090SBjorn Andersson continue; 212c97c4090SBjorn Andersson 213c97c4090SBjorn Andersson if (val & BIT(i)) { 214c97c4090SBjorn Andersson if (test_bit(i, entry->irq_rising)) { 215c97c4090SBjorn Andersson irq_pin = irq_find_mapping(entry->domain, i); 216c97c4090SBjorn Andersson handle_nested_irq(irq_pin); 217c97c4090SBjorn Andersson } 218c97c4090SBjorn Andersson } else { 219c97c4090SBjorn Andersson if (test_bit(i, entry->irq_falling)) { 220c97c4090SBjorn Andersson irq_pin = irq_find_mapping(entry->domain, i); 221c97c4090SBjorn Andersson handle_nested_irq(irq_pin); 222c97c4090SBjorn Andersson } 223c97c4090SBjorn Andersson } 224c97c4090SBjorn Andersson } 225c97c4090SBjorn Andersson 226c97c4090SBjorn Andersson return IRQ_HANDLED; 227c97c4090SBjorn Andersson } 228c97c4090SBjorn Andersson 229c97c4090SBjorn Andersson /** 230c97c4090SBjorn Andersson * smsm_mask_irq() - un-subscribe from cascades of IRQs of a certain staus bit 231c97c4090SBjorn Andersson * @irqd: IRQ handle to be masked 232c97c4090SBjorn Andersson * 233c97c4090SBjorn Andersson * This un-subscribes the local CPU from interrupts upon changes to the defines 234c97c4090SBjorn Andersson * status bit. The bit is also cleared from cascading. 235c97c4090SBjorn Andersson */ 236c97c4090SBjorn Andersson static void smsm_mask_irq(struct irq_data *irqd) 237c97c4090SBjorn Andersson { 238c97c4090SBjorn Andersson struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd); 239c97c4090SBjorn Andersson irq_hw_number_t irq = irqd_to_hwirq(irqd); 240c97c4090SBjorn Andersson struct qcom_smsm *smsm = entry->smsm; 241c97c4090SBjorn Andersson u32 val; 242c97c4090SBjorn Andersson 243c97c4090SBjorn Andersson if (entry->subscription) { 244c97c4090SBjorn Andersson val = readl(entry->subscription + smsm->local_host); 245c97c4090SBjorn Andersson val &= ~BIT(irq); 246c97c4090SBjorn Andersson writel(val, entry->subscription + smsm->local_host); 247c97c4090SBjorn Andersson } 248c97c4090SBjorn Andersson 249c97c4090SBjorn Andersson clear_bit(irq, entry->irq_enabled); 250c97c4090SBjorn Andersson } 251c97c4090SBjorn Andersson 252c97c4090SBjorn Andersson /** 253c97c4090SBjorn Andersson * smsm_unmask_irq() - subscribe to cascades of IRQs of a certain status bit 254c97c4090SBjorn Andersson * @irqd: IRQ handle to be unmasked 255c97c4090SBjorn Andersson * 256c97c4090SBjorn Andersson * This subscribes the local CPU to interrupts upon changes to the defined 257c97c4090SBjorn Andersson * status bit. The bit is also marked for cascading. 258c97c4090SBjorn Andersson */ 259c97c4090SBjorn Andersson static void smsm_unmask_irq(struct irq_data *irqd) 260c97c4090SBjorn Andersson { 261c97c4090SBjorn Andersson struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd); 262c97c4090SBjorn Andersson irq_hw_number_t irq = irqd_to_hwirq(irqd); 263c97c4090SBjorn Andersson struct qcom_smsm *smsm = entry->smsm; 264c97c4090SBjorn Andersson u32 val; 265c97c4090SBjorn Andersson 266e3d45719SStephan Gerhold /* Make sure our last cached state is up-to-date */ 267e3d45719SStephan Gerhold if (readl(entry->remote_state) & BIT(irq)) 268e3d45719SStephan Gerhold set_bit(irq, &entry->last_value); 269e3d45719SStephan Gerhold else 270e3d45719SStephan Gerhold clear_bit(irq, &entry->last_value); 271e3d45719SStephan Gerhold 272c97c4090SBjorn Andersson set_bit(irq, entry->irq_enabled); 273c97c4090SBjorn Andersson 274c97c4090SBjorn Andersson if (entry->subscription) { 275c97c4090SBjorn Andersson val = readl(entry->subscription + smsm->local_host); 276c97c4090SBjorn Andersson val |= BIT(irq); 277c97c4090SBjorn Andersson writel(val, entry->subscription + smsm->local_host); 278c97c4090SBjorn Andersson } 279c97c4090SBjorn Andersson } 280c97c4090SBjorn Andersson 281c97c4090SBjorn Andersson /** 282c97c4090SBjorn Andersson * smsm_set_irq_type() - updates the requested IRQ type for the cascading 283c97c4090SBjorn Andersson * @irqd: consumer interrupt handle 284c97c4090SBjorn Andersson * @type: requested flags 285c97c4090SBjorn Andersson */ 286c97c4090SBjorn Andersson static int smsm_set_irq_type(struct irq_data *irqd, unsigned int type) 287c97c4090SBjorn Andersson { 288c97c4090SBjorn Andersson struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd); 289c97c4090SBjorn Andersson irq_hw_number_t irq = irqd_to_hwirq(irqd); 290c97c4090SBjorn Andersson 291c97c4090SBjorn Andersson if (!(type & IRQ_TYPE_EDGE_BOTH)) 292c97c4090SBjorn Andersson return -EINVAL; 293c97c4090SBjorn Andersson 294c97c4090SBjorn Andersson if (type & IRQ_TYPE_EDGE_RISING) 295c97c4090SBjorn Andersson set_bit(irq, entry->irq_rising); 296c97c4090SBjorn Andersson else 297c97c4090SBjorn Andersson clear_bit(irq, entry->irq_rising); 298c97c4090SBjorn Andersson 299c97c4090SBjorn Andersson if (type & IRQ_TYPE_EDGE_FALLING) 300c97c4090SBjorn Andersson set_bit(irq, entry->irq_falling); 301c97c4090SBjorn Andersson else 302c97c4090SBjorn Andersson clear_bit(irq, entry->irq_falling); 303c97c4090SBjorn Andersson 304c97c4090SBjorn Andersson return 0; 305c97c4090SBjorn Andersson } 306c97c4090SBjorn Andersson 307c73a6852SStephan Gerhold static int smsm_get_irqchip_state(struct irq_data *irqd, 308c73a6852SStephan Gerhold enum irqchip_irq_state which, bool *state) 309c73a6852SStephan Gerhold { 310c73a6852SStephan Gerhold struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd); 311c73a6852SStephan Gerhold irq_hw_number_t irq = irqd_to_hwirq(irqd); 312c73a6852SStephan Gerhold u32 val; 313c73a6852SStephan Gerhold 314c73a6852SStephan Gerhold if (which != IRQCHIP_STATE_LINE_LEVEL) 315c73a6852SStephan Gerhold return -EINVAL; 316c73a6852SStephan Gerhold 317c73a6852SStephan Gerhold val = readl(entry->remote_state); 318c73a6852SStephan Gerhold *state = !!(val & BIT(irq)); 319c73a6852SStephan Gerhold 320c73a6852SStephan Gerhold return 0; 321c73a6852SStephan Gerhold } 322c73a6852SStephan Gerhold 323c97c4090SBjorn Andersson static struct irq_chip smsm_irq_chip = { 324c97c4090SBjorn Andersson .name = "smsm", 325c97c4090SBjorn Andersson .irq_mask = smsm_mask_irq, 326c97c4090SBjorn Andersson .irq_unmask = smsm_unmask_irq, 327c97c4090SBjorn Andersson .irq_set_type = smsm_set_irq_type, 328c73a6852SStephan Gerhold .irq_get_irqchip_state = smsm_get_irqchip_state, 329c97c4090SBjorn Andersson }; 330c97c4090SBjorn Andersson 331c97c4090SBjorn Andersson /** 332c97c4090SBjorn Andersson * smsm_irq_map() - sets up a mapping for a cascaded IRQ 333c97c4090SBjorn Andersson * @d: IRQ domain representing an entry 334c97c4090SBjorn Andersson * @irq: IRQ to set up 335c97c4090SBjorn Andersson * @hw: unused 336c97c4090SBjorn Andersson */ 337c97c4090SBjorn Andersson static int smsm_irq_map(struct irq_domain *d, 338c97c4090SBjorn Andersson unsigned int irq, 339c97c4090SBjorn Andersson irq_hw_number_t hw) 340c97c4090SBjorn Andersson { 341c97c4090SBjorn Andersson struct smsm_entry *entry = d->host_data; 342c97c4090SBjorn Andersson 343c97c4090SBjorn Andersson irq_set_chip_and_handler(irq, &smsm_irq_chip, handle_level_irq); 344c97c4090SBjorn Andersson irq_set_chip_data(irq, entry); 345c97c4090SBjorn Andersson irq_set_nested_thread(irq, 1); 346c97c4090SBjorn Andersson 347c97c4090SBjorn Andersson return 0; 348c97c4090SBjorn Andersson } 349c97c4090SBjorn Andersson 350c97c4090SBjorn Andersson static const struct irq_domain_ops smsm_irq_ops = { 351c97c4090SBjorn Andersson .map = smsm_irq_map, 352c97c4090SBjorn Andersson .xlate = irq_domain_xlate_twocell, 353c97c4090SBjorn Andersson }; 354c97c4090SBjorn Andersson 355c97c4090SBjorn Andersson /** 356c97c4090SBjorn Andersson * smsm_parse_ipc() - parses a qcom,ipc-%d device tree property 357c97c4090SBjorn Andersson * @smsm: smsm driver context 358c97c4090SBjorn Andersson * @host_id: index of the remote host to be resolved 359c97c4090SBjorn Andersson * 360c97c4090SBjorn Andersson * Parses device tree to acquire the information needed for sending the 361c97c4090SBjorn Andersson * outgoing interrupts to a remote host - identified by @host_id. 362c97c4090SBjorn Andersson */ 363c97c4090SBjorn Andersson static int smsm_parse_ipc(struct qcom_smsm *smsm, unsigned host_id) 364c97c4090SBjorn Andersson { 365c97c4090SBjorn Andersson struct device_node *syscon; 366c97c4090SBjorn Andersson struct device_node *node = smsm->dev->of_node; 367c97c4090SBjorn Andersson struct smsm_host *host = &smsm->hosts[host_id]; 368c97c4090SBjorn Andersson char key[16]; 369c97c4090SBjorn Andersson int ret; 370c97c4090SBjorn Andersson 371c97c4090SBjorn Andersson snprintf(key, sizeof(key), "qcom,ipc-%d", host_id); 372c97c4090SBjorn Andersson syscon = of_parse_phandle(node, key, 0); 373c97c4090SBjorn Andersson if (!syscon) 374c97c4090SBjorn Andersson return 0; 375c97c4090SBjorn Andersson 376c97c4090SBjorn Andersson host->ipc_regmap = syscon_node_to_regmap(syscon); 377aad66a3cSMiaoqian Lin of_node_put(syscon); 378c97c4090SBjorn Andersson if (IS_ERR(host->ipc_regmap)) 379c97c4090SBjorn Andersson return PTR_ERR(host->ipc_regmap); 380c97c4090SBjorn Andersson 381c97c4090SBjorn Andersson ret = of_property_read_u32_index(node, key, 1, &host->ipc_offset); 382c97c4090SBjorn Andersson if (ret < 0) { 383c97c4090SBjorn Andersson dev_err(smsm->dev, "no offset in %s\n", key); 384c97c4090SBjorn Andersson return -EINVAL; 385c97c4090SBjorn Andersson } 386c97c4090SBjorn Andersson 387c97c4090SBjorn Andersson ret = of_property_read_u32_index(node, key, 2, &host->ipc_bit); 388c97c4090SBjorn Andersson if (ret < 0) { 389c97c4090SBjorn Andersson dev_err(smsm->dev, "no bit in %s\n", key); 390c97c4090SBjorn Andersson return -EINVAL; 391c97c4090SBjorn Andersson } 392c97c4090SBjorn Andersson 393c97c4090SBjorn Andersson return 0; 394c97c4090SBjorn Andersson } 395c97c4090SBjorn Andersson 396c97c4090SBjorn Andersson /** 397c97c4090SBjorn Andersson * smsm_inbound_entry() - parse DT and set up an entry representing a remote system 398c97c4090SBjorn Andersson * @smsm: smsm driver context 399c97c4090SBjorn Andersson * @entry: entry context to be set up 400c97c4090SBjorn Andersson * @node: dt node containing the entry's properties 401c97c4090SBjorn Andersson */ 402c97c4090SBjorn Andersson static int smsm_inbound_entry(struct qcom_smsm *smsm, 403c97c4090SBjorn Andersson struct smsm_entry *entry, 404c97c4090SBjorn Andersson struct device_node *node) 405c97c4090SBjorn Andersson { 406c97c4090SBjorn Andersson int ret; 407c97c4090SBjorn Andersson int irq; 408c97c4090SBjorn Andersson 409c97c4090SBjorn Andersson irq = irq_of_parse_and_map(node, 0); 410c97c4090SBjorn Andersson if (!irq) { 411c97c4090SBjorn Andersson dev_err(smsm->dev, "failed to parse smsm interrupt\n"); 412c97c4090SBjorn Andersson return -EINVAL; 413c97c4090SBjorn Andersson } 414c97c4090SBjorn Andersson 415c97c4090SBjorn Andersson ret = devm_request_threaded_irq(smsm->dev, irq, 416c97c4090SBjorn Andersson NULL, smsm_intr, 417c97c4090SBjorn Andersson IRQF_ONESHOT, 418c97c4090SBjorn Andersson "smsm", (void *)entry); 419c97c4090SBjorn Andersson if (ret) { 420c97c4090SBjorn Andersson dev_err(smsm->dev, "failed to request interrupt\n"); 421c97c4090SBjorn Andersson return ret; 422c97c4090SBjorn Andersson } 423c97c4090SBjorn Andersson 424c97c4090SBjorn Andersson entry->domain = irq_domain_add_linear(node, 32, &smsm_irq_ops, entry); 425c97c4090SBjorn Andersson if (!entry->domain) { 426c97c4090SBjorn Andersson dev_err(smsm->dev, "failed to add irq_domain\n"); 427c97c4090SBjorn Andersson return -ENOMEM; 428c97c4090SBjorn Andersson } 429c97c4090SBjorn Andersson 430c97c4090SBjorn Andersson return 0; 431c97c4090SBjorn Andersson } 432c97c4090SBjorn Andersson 433c97c4090SBjorn Andersson /** 434c97c4090SBjorn Andersson * smsm_get_size_info() - parse the optional memory segment for sizes 435c97c4090SBjorn Andersson * @smsm: smsm driver context 436c97c4090SBjorn Andersson * 437c97c4090SBjorn Andersson * Attempt to acquire the number of hosts and entries from the optional shared 438c97c4090SBjorn Andersson * memory location. Not being able to find this segment should indicate that 439c97c4090SBjorn Andersson * we're on a older system where these values was hard coded to 440c97c4090SBjorn Andersson * SMSM_DEFAULT_NUM_ENTRIES and SMSM_DEFAULT_NUM_HOSTS. 441c97c4090SBjorn Andersson * 442c97c4090SBjorn Andersson * Returns 0 on success, negative errno on failure. 443c97c4090SBjorn Andersson */ 444c97c4090SBjorn Andersson static int smsm_get_size_info(struct qcom_smsm *smsm) 445c97c4090SBjorn Andersson { 446c97c4090SBjorn Andersson size_t size; 447c97c4090SBjorn Andersson struct { 448c97c4090SBjorn Andersson u32 num_hosts; 449c97c4090SBjorn Andersson u32 num_entries; 450c97c4090SBjorn Andersson u32 reserved0; 451c97c4090SBjorn Andersson u32 reserved1; 452c97c4090SBjorn Andersson } *info; 453c97c4090SBjorn Andersson 454c97c4090SBjorn Andersson info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SIZE_INFO, &size); 45535dfa3efSJonathan Neuschäfer if (IS_ERR(info) && PTR_ERR(info) != -ENOENT) { 45635dfa3efSJonathan Neuschäfer if (PTR_ERR(info) != -EPROBE_DEFER) 45735dfa3efSJonathan Neuschäfer dev_err(smsm->dev, "unable to retrieve smsm size info\n"); 45835dfa3efSJonathan Neuschäfer return PTR_ERR(info); 45935dfa3efSJonathan Neuschäfer } else if (IS_ERR(info) || size != sizeof(*info)) { 460c97c4090SBjorn Andersson dev_warn(smsm->dev, "no smsm size info, using defaults\n"); 461c97c4090SBjorn Andersson smsm->num_entries = SMSM_DEFAULT_NUM_ENTRIES; 462c97c4090SBjorn Andersson smsm->num_hosts = SMSM_DEFAULT_NUM_HOSTS; 463c97c4090SBjorn Andersson return 0; 464c97c4090SBjorn Andersson } 465c97c4090SBjorn Andersson 466c97c4090SBjorn Andersson smsm->num_entries = info->num_entries; 467c97c4090SBjorn Andersson smsm->num_hosts = info->num_hosts; 468c97c4090SBjorn Andersson 469c97c4090SBjorn Andersson dev_dbg(smsm->dev, 470c97c4090SBjorn Andersson "found custom size of smsm: %d entries %d hosts\n", 471c97c4090SBjorn Andersson smsm->num_entries, smsm->num_hosts); 472c97c4090SBjorn Andersson 473c97c4090SBjorn Andersson return 0; 474c97c4090SBjorn Andersson } 475c97c4090SBjorn Andersson 476c97c4090SBjorn Andersson static int qcom_smsm_probe(struct platform_device *pdev) 477c97c4090SBjorn Andersson { 478c97c4090SBjorn Andersson struct device_node *local_node; 479c97c4090SBjorn Andersson struct device_node *node; 480c97c4090SBjorn Andersson struct smsm_entry *entry; 481c97c4090SBjorn Andersson struct qcom_smsm *smsm; 482c97c4090SBjorn Andersson u32 *intr_mask; 483c97c4090SBjorn Andersson size_t size; 484c97c4090SBjorn Andersson u32 *states; 485c97c4090SBjorn Andersson u32 id; 486c97c4090SBjorn Andersson int ret; 487c97c4090SBjorn Andersson 488c97c4090SBjorn Andersson smsm = devm_kzalloc(&pdev->dev, sizeof(*smsm), GFP_KERNEL); 489c97c4090SBjorn Andersson if (!smsm) 490c97c4090SBjorn Andersson return -ENOMEM; 491c97c4090SBjorn Andersson smsm->dev = &pdev->dev; 492c97c4090SBjorn Andersson spin_lock_init(&smsm->lock); 493c97c4090SBjorn Andersson 494c97c4090SBjorn Andersson ret = smsm_get_size_info(smsm); 495c97c4090SBjorn Andersson if (ret) 496c97c4090SBjorn Andersson return ret; 497c97c4090SBjorn Andersson 498c97c4090SBjorn Andersson smsm->entries = devm_kcalloc(&pdev->dev, 499c97c4090SBjorn Andersson smsm->num_entries, 500c97c4090SBjorn Andersson sizeof(struct smsm_entry), 501c97c4090SBjorn Andersson GFP_KERNEL); 502c97c4090SBjorn Andersson if (!smsm->entries) 503c97c4090SBjorn Andersson return -ENOMEM; 504c97c4090SBjorn Andersson 505c97c4090SBjorn Andersson smsm->hosts = devm_kcalloc(&pdev->dev, 506c97c4090SBjorn Andersson smsm->num_hosts, 507c97c4090SBjorn Andersson sizeof(struct smsm_host), 508c97c4090SBjorn Andersson GFP_KERNEL); 509c97c4090SBjorn Andersson if (!smsm->hosts) 510c97c4090SBjorn Andersson return -ENOMEM; 511c97c4090SBjorn Andersson 5128804517eSJohan Hovold for_each_child_of_node(pdev->dev.of_node, local_node) { 513*4a1b9f4eSRob Herring if (of_property_present(local_node, "#qcom,smem-state-cells")) 5148804517eSJohan Hovold break; 5158804517eSJohan Hovold } 516c97c4090SBjorn Andersson if (!local_node) { 517c97c4090SBjorn Andersson dev_err(&pdev->dev, "no state entry\n"); 518c97c4090SBjorn Andersson return -EINVAL; 519c97c4090SBjorn Andersson } 520c97c4090SBjorn Andersson 521c97c4090SBjorn Andersson of_property_read_u32(pdev->dev.of_node, 522c97c4090SBjorn Andersson "qcom,local-host", 523c97c4090SBjorn Andersson &smsm->local_host); 524c97c4090SBjorn Andersson 525c97c4090SBjorn Andersson /* Parse the host properties */ 526c97c4090SBjorn Andersson for (id = 0; id < smsm->num_hosts; id++) { 527c97c4090SBjorn Andersson ret = smsm_parse_ipc(smsm, id); 528c97c4090SBjorn Andersson if (ret < 0) 529af8f6f39SLiang He goto out_put; 530c97c4090SBjorn Andersson } 531c97c4090SBjorn Andersson 532c97c4090SBjorn Andersson /* Acquire the main SMSM state vector */ 533c97c4090SBjorn Andersson ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE, 534c97c4090SBjorn Andersson smsm->num_entries * sizeof(u32)); 535c97c4090SBjorn Andersson if (ret < 0 && ret != -EEXIST) { 536c97c4090SBjorn Andersson dev_err(&pdev->dev, "unable to allocate shared state entry\n"); 537af8f6f39SLiang He goto out_put; 538c97c4090SBjorn Andersson } 539c97c4090SBjorn Andersson 540c97c4090SBjorn Andersson states = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE, NULL); 541c97c4090SBjorn Andersson if (IS_ERR(states)) { 542c97c4090SBjorn Andersson dev_err(&pdev->dev, "Unable to acquire shared state entry\n"); 543af8f6f39SLiang He ret = PTR_ERR(states); 544af8f6f39SLiang He goto out_put; 545c97c4090SBjorn Andersson } 546c97c4090SBjorn Andersson 547c97c4090SBjorn Andersson /* Acquire the list of interrupt mask vectors */ 548c97c4090SBjorn Andersson size = smsm->num_entries * smsm->num_hosts * sizeof(u32); 549c97c4090SBjorn Andersson ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, size); 550c97c4090SBjorn Andersson if (ret < 0 && ret != -EEXIST) { 551c97c4090SBjorn Andersson dev_err(&pdev->dev, "unable to allocate smsm interrupt mask\n"); 552af8f6f39SLiang He goto out_put; 553c97c4090SBjorn Andersson } 554c97c4090SBjorn Andersson 555c97c4090SBjorn Andersson intr_mask = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, NULL); 556c97c4090SBjorn Andersson if (IS_ERR(intr_mask)) { 557c97c4090SBjorn Andersson dev_err(&pdev->dev, "unable to acquire shared memory interrupt mask\n"); 558af8f6f39SLiang He ret = PTR_ERR(intr_mask); 559af8f6f39SLiang He goto out_put; 560c97c4090SBjorn Andersson } 561c97c4090SBjorn Andersson 562c97c4090SBjorn Andersson /* Setup the reference to the local state bits */ 563c97c4090SBjorn Andersson smsm->local_state = states + smsm->local_host; 564c97c4090SBjorn Andersson smsm->subscription = intr_mask + smsm->local_host * smsm->num_hosts; 565c97c4090SBjorn Andersson 566c97c4090SBjorn Andersson /* Register the outgoing state */ 567c97c4090SBjorn Andersson smsm->state = qcom_smem_state_register(local_node, &smsm_state_ops, smsm); 568c97c4090SBjorn Andersson if (IS_ERR(smsm->state)) { 569c97c4090SBjorn Andersson dev_err(smsm->dev, "failed to register qcom_smem_state\n"); 570af8f6f39SLiang He ret = PTR_ERR(smsm->state); 571af8f6f39SLiang He goto out_put; 572c97c4090SBjorn Andersson } 573c97c4090SBjorn Andersson 574c97c4090SBjorn Andersson /* Register handlers for remote processor entries of interest. */ 575c97c4090SBjorn Andersson for_each_available_child_of_node(pdev->dev.of_node, node) { 576c97c4090SBjorn Andersson if (!of_property_read_bool(node, "interrupt-controller")) 577c97c4090SBjorn Andersson continue; 578c97c4090SBjorn Andersson 579c97c4090SBjorn Andersson ret = of_property_read_u32(node, "reg", &id); 580c97c4090SBjorn Andersson if (ret || id >= smsm->num_entries) { 581c97c4090SBjorn Andersson dev_err(&pdev->dev, "invalid reg of entry\n"); 582c97c4090SBjorn Andersson if (!ret) 583c97c4090SBjorn Andersson ret = -EINVAL; 584c97c4090SBjorn Andersson goto unwind_interfaces; 585c97c4090SBjorn Andersson } 586c97c4090SBjorn Andersson entry = &smsm->entries[id]; 587c97c4090SBjorn Andersson 588c97c4090SBjorn Andersson entry->smsm = smsm; 589c97c4090SBjorn Andersson entry->remote_state = states + id; 590c97c4090SBjorn Andersson 591c97c4090SBjorn Andersson /* Setup subscription pointers and unsubscribe to any kicks */ 592c97c4090SBjorn Andersson entry->subscription = intr_mask + id * smsm->num_hosts; 593c97c4090SBjorn Andersson writel(0, entry->subscription + smsm->local_host); 594c97c4090SBjorn Andersson 595c97c4090SBjorn Andersson ret = smsm_inbound_entry(smsm, entry, node); 596c97c4090SBjorn Andersson if (ret < 0) 597c97c4090SBjorn Andersson goto unwind_interfaces; 598c97c4090SBjorn Andersson } 599c97c4090SBjorn Andersson 600c97c4090SBjorn Andersson platform_set_drvdata(pdev, smsm); 601af8f6f39SLiang He of_node_put(local_node); 602c97c4090SBjorn Andersson 603c97c4090SBjorn Andersson return 0; 604c97c4090SBjorn Andersson 605c97c4090SBjorn Andersson unwind_interfaces: 606af8f6f39SLiang He of_node_put(node); 607c97c4090SBjorn Andersson for (id = 0; id < smsm->num_entries; id++) 608c97c4090SBjorn Andersson if (smsm->entries[id].domain) 609c97c4090SBjorn Andersson irq_domain_remove(smsm->entries[id].domain); 610c97c4090SBjorn Andersson 611c97c4090SBjorn Andersson qcom_smem_state_unregister(smsm->state); 612af8f6f39SLiang He out_put: 613af8f6f39SLiang He of_node_put(local_node); 614c97c4090SBjorn Andersson return ret; 615c97c4090SBjorn Andersson } 616c97c4090SBjorn Andersson 617c97c4090SBjorn Andersson static int qcom_smsm_remove(struct platform_device *pdev) 618c97c4090SBjorn Andersson { 619c97c4090SBjorn Andersson struct qcom_smsm *smsm = platform_get_drvdata(pdev); 620c97c4090SBjorn Andersson unsigned id; 621c97c4090SBjorn Andersson 622c97c4090SBjorn Andersson for (id = 0; id < smsm->num_entries; id++) 623c97c4090SBjorn Andersson if (smsm->entries[id].domain) 624c97c4090SBjorn Andersson irq_domain_remove(smsm->entries[id].domain); 625c97c4090SBjorn Andersson 626c97c4090SBjorn Andersson qcom_smem_state_unregister(smsm->state); 627c97c4090SBjorn Andersson 628c97c4090SBjorn Andersson return 0; 629c97c4090SBjorn Andersson } 630c97c4090SBjorn Andersson 631c97c4090SBjorn Andersson static const struct of_device_id qcom_smsm_of_match[] = { 632c97c4090SBjorn Andersson { .compatible = "qcom,smsm" }, 633c97c4090SBjorn Andersson {} 634c97c4090SBjorn Andersson }; 635c97c4090SBjorn Andersson MODULE_DEVICE_TABLE(of, qcom_smsm_of_match); 636c97c4090SBjorn Andersson 637c97c4090SBjorn Andersson static struct platform_driver qcom_smsm_driver = { 638c97c4090SBjorn Andersson .probe = qcom_smsm_probe, 639c97c4090SBjorn Andersson .remove = qcom_smsm_remove, 640c97c4090SBjorn Andersson .driver = { 641c97c4090SBjorn Andersson .name = "qcom-smsm", 642c97c4090SBjorn Andersson .of_match_table = qcom_smsm_of_match, 643c97c4090SBjorn Andersson }, 644c97c4090SBjorn Andersson }; 645c97c4090SBjorn Andersson module_platform_driver(qcom_smsm_driver); 646c97c4090SBjorn Andersson 647c97c4090SBjorn Andersson MODULE_DESCRIPTION("Qualcomm Shared Memory State Machine driver"); 648c97c4090SBjorn Andersson MODULE_LICENSE("GPL v2"); 649