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