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 * @mask:       value mask
 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 * This subscribes the local CPU to interrupts upon changes to the defined
 258 * status bit. The bit is also marked for cascading.
 259 */
 260static void smsm_unmask_irq(struct irq_data *irqd)
 261{
 262        struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
 263        irq_hw_number_t irq = irqd_to_hwirq(irqd);
 264        struct qcom_smsm *smsm = entry->smsm;
 265        u32 val;
 266
 267        set_bit(irq, entry->irq_enabled);
 268
 269        if (entry->subscription) {
 270                val = readl(entry->subscription + smsm->local_host);
 271                val |= BIT(irq);
 272                writel(val, entry->subscription + smsm->local_host);
 273        }
 274}
 275
 276/**
 277 * smsm_set_irq_type() - updates the requested IRQ type for the cascading
 278 * @irqd:       consumer interrupt handle
 279 * @type:       requested flags
 280 */
 281static int smsm_set_irq_type(struct irq_data *irqd, unsigned int type)
 282{
 283        struct smsm_entry *entry = irq_data_get_irq_chip_data(irqd);
 284        irq_hw_number_t irq = irqd_to_hwirq(irqd);
 285
 286        if (!(type & IRQ_TYPE_EDGE_BOTH))
 287                return -EINVAL;
 288
 289        if (type & IRQ_TYPE_EDGE_RISING)
 290                set_bit(irq, entry->irq_rising);
 291        else
 292                clear_bit(irq, entry->irq_rising);
 293
 294        if (type & IRQ_TYPE_EDGE_FALLING)
 295                set_bit(irq, entry->irq_falling);
 296        else
 297                clear_bit(irq, entry->irq_falling);
 298
 299        return 0;
 300}
 301
 302static struct irq_chip smsm_irq_chip = {
 303        .name           = "smsm",
 304        .irq_mask       = smsm_mask_irq,
 305        .irq_unmask     = smsm_unmask_irq,
 306        .irq_set_type   = smsm_set_irq_type,
 307};
 308
 309/**
 310 * smsm_irq_map() - sets up a mapping for a cascaded IRQ
 311 * @d:          IRQ domain representing an entry
 312 * @irq:        IRQ to set up
 313 * @hw:         unused
 314 */
 315static int smsm_irq_map(struct irq_domain *d,
 316                        unsigned int irq,
 317                        irq_hw_number_t hw)
 318{
 319        struct smsm_entry *entry = d->host_data;
 320
 321        irq_set_chip_and_handler(irq, &smsm_irq_chip, handle_level_irq);
 322        irq_set_chip_data(irq, entry);
 323        irq_set_nested_thread(irq, 1);
 324
 325        return 0;
 326}
 327
 328static const struct irq_domain_ops smsm_irq_ops = {
 329        .map = smsm_irq_map,
 330        .xlate = irq_domain_xlate_twocell,
 331};
 332
 333/**
 334 * smsm_parse_ipc() - parses a qcom,ipc-%d device tree property
 335 * @smsm:       smsm driver context
 336 * @host_id:    index of the remote host to be resolved
 337 *
 338 * Parses device tree to acquire the information needed for sending the
 339 * outgoing interrupts to a remote host - identified by @host_id.
 340 */
 341static int smsm_parse_ipc(struct qcom_smsm *smsm, unsigned host_id)
 342{
 343        struct device_node *syscon;
 344        struct device_node *node = smsm->dev->of_node;
 345        struct smsm_host *host = &smsm->hosts[host_id];
 346        char key[16];
 347        int ret;
 348
 349        snprintf(key, sizeof(key), "qcom,ipc-%d", host_id);
 350        syscon = of_parse_phandle(node, key, 0);
 351        if (!syscon)
 352                return 0;
 353
 354        host->ipc_regmap = syscon_node_to_regmap(syscon);
 355        if (IS_ERR(host->ipc_regmap))
 356                return PTR_ERR(host->ipc_regmap);
 357
 358        ret = of_property_read_u32_index(node, key, 1, &host->ipc_offset);
 359        if (ret < 0) {
 360                dev_err(smsm->dev, "no offset in %s\n", key);
 361                return -EINVAL;
 362        }
 363
 364        ret = of_property_read_u32_index(node, key, 2, &host->ipc_bit);
 365        if (ret < 0) {
 366                dev_err(smsm->dev, "no bit in %s\n", key);
 367                return -EINVAL;
 368        }
 369
 370        return 0;
 371}
 372
 373/**
 374 * smsm_inbound_entry() - parse DT and set up an entry representing a remote system
 375 * @smsm:       smsm driver context
 376 * @entry:      entry context to be set up
 377 * @node:       dt node containing the entry's properties
 378 */
 379static int smsm_inbound_entry(struct qcom_smsm *smsm,
 380                              struct smsm_entry *entry,
 381                              struct device_node *node)
 382{
 383        int ret;
 384        int irq;
 385
 386        irq = irq_of_parse_and_map(node, 0);
 387        if (!irq) {
 388                dev_err(smsm->dev, "failed to parse smsm interrupt\n");
 389                return -EINVAL;
 390        }
 391
 392        ret = devm_request_threaded_irq(smsm->dev, irq,
 393                                        NULL, smsm_intr,
 394                                        IRQF_ONESHOT,
 395                                        "smsm", (void *)entry);
 396        if (ret) {
 397                dev_err(smsm->dev, "failed to request interrupt\n");
 398                return ret;
 399        }
 400
 401        entry->domain = irq_domain_add_linear(node, 32, &smsm_irq_ops, entry);
 402        if (!entry->domain) {
 403                dev_err(smsm->dev, "failed to add irq_domain\n");
 404                return -ENOMEM;
 405        }
 406
 407        return 0;
 408}
 409
 410/**
 411 * smsm_get_size_info() - parse the optional memory segment for sizes
 412 * @smsm:       smsm driver context
 413 *
 414 * Attempt to acquire the number of hosts and entries from the optional shared
 415 * memory location. Not being able to find this segment should indicate that
 416 * we're on a older system where these values was hard coded to
 417 * SMSM_DEFAULT_NUM_ENTRIES and SMSM_DEFAULT_NUM_HOSTS.
 418 *
 419 * Returns 0 on success, negative errno on failure.
 420 */
 421static int smsm_get_size_info(struct qcom_smsm *smsm)
 422{
 423        size_t size;
 424        struct {
 425                u32 num_hosts;
 426                u32 num_entries;
 427                u32 reserved0;
 428                u32 reserved1;
 429        } *info;
 430
 431        info = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SIZE_INFO, &size);
 432        if (IS_ERR(info) && PTR_ERR(info) != -ENOENT) {
 433                if (PTR_ERR(info) != -EPROBE_DEFER)
 434                        dev_err(smsm->dev, "unable to retrieve smsm size info\n");
 435                return PTR_ERR(info);
 436        } else if (IS_ERR(info) || size != sizeof(*info)) {
 437                dev_warn(smsm->dev, "no smsm size info, using defaults\n");
 438                smsm->num_entries = SMSM_DEFAULT_NUM_ENTRIES;
 439                smsm->num_hosts = SMSM_DEFAULT_NUM_HOSTS;
 440                return 0;
 441        }
 442
 443        smsm->num_entries = info->num_entries;
 444        smsm->num_hosts = info->num_hosts;
 445
 446        dev_dbg(smsm->dev,
 447                "found custom size of smsm: %d entries %d hosts\n",
 448                smsm->num_entries, smsm->num_hosts);
 449
 450        return 0;
 451}
 452
 453static int qcom_smsm_probe(struct platform_device *pdev)
 454{
 455        struct device_node *local_node;
 456        struct device_node *node;
 457        struct smsm_entry *entry;
 458        struct qcom_smsm *smsm;
 459        u32 *intr_mask;
 460        size_t size;
 461        u32 *states;
 462        u32 id;
 463        int ret;
 464
 465        smsm = devm_kzalloc(&pdev->dev, sizeof(*smsm), GFP_KERNEL);
 466        if (!smsm)
 467                return -ENOMEM;
 468        smsm->dev = &pdev->dev;
 469        spin_lock_init(&smsm->lock);
 470
 471        ret = smsm_get_size_info(smsm);
 472        if (ret)
 473                return ret;
 474
 475        smsm->entries = devm_kcalloc(&pdev->dev,
 476                                     smsm->num_entries,
 477                                     sizeof(struct smsm_entry),
 478                                     GFP_KERNEL);
 479        if (!smsm->entries)
 480                return -ENOMEM;
 481
 482        smsm->hosts = devm_kcalloc(&pdev->dev,
 483                                   smsm->num_hosts,
 484                                   sizeof(struct smsm_host),
 485                                   GFP_KERNEL);
 486        if (!smsm->hosts)
 487                return -ENOMEM;
 488
 489        for_each_child_of_node(pdev->dev.of_node, local_node) {
 490                if (of_find_property(local_node, "#qcom,smem-state-cells", NULL))
 491                        break;
 492        }
 493        if (!local_node) {
 494                dev_err(&pdev->dev, "no state entry\n");
 495                return -EINVAL;
 496        }
 497
 498        of_property_read_u32(pdev->dev.of_node,
 499                             "qcom,local-host",
 500                             &smsm->local_host);
 501
 502        /* Parse the host properties */
 503        for (id = 0; id < smsm->num_hosts; id++) {
 504                ret = smsm_parse_ipc(smsm, id);
 505                if (ret < 0)
 506                        return ret;
 507        }
 508
 509        /* Acquire the main SMSM state vector */
 510        ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE,
 511                              smsm->num_entries * sizeof(u32));
 512        if (ret < 0 && ret != -EEXIST) {
 513                dev_err(&pdev->dev, "unable to allocate shared state entry\n");
 514                return ret;
 515        }
 516
 517        states = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_SHARED_STATE, NULL);
 518        if (IS_ERR(states)) {
 519                dev_err(&pdev->dev, "Unable to acquire shared state entry\n");
 520                return PTR_ERR(states);
 521        }
 522
 523        /* Acquire the list of interrupt mask vectors */
 524        size = smsm->num_entries * smsm->num_hosts * sizeof(u32);
 525        ret = qcom_smem_alloc(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, size);
 526        if (ret < 0 && ret != -EEXIST) {
 527                dev_err(&pdev->dev, "unable to allocate smsm interrupt mask\n");
 528                return ret;
 529        }
 530
 531        intr_mask = qcom_smem_get(QCOM_SMEM_HOST_ANY, SMEM_SMSM_CPU_INTR_MASK, NULL);
 532        if (IS_ERR(intr_mask)) {
 533                dev_err(&pdev->dev, "unable to acquire shared memory interrupt mask\n");
 534                return PTR_ERR(intr_mask);
 535        }
 536
 537        /* Setup the reference to the local state bits */
 538        smsm->local_state = states + smsm->local_host;
 539        smsm->subscription = intr_mask + smsm->local_host * smsm->num_hosts;
 540
 541        /* Register the outgoing state */
 542        smsm->state = qcom_smem_state_register(local_node, &smsm_state_ops, smsm);
 543        if (IS_ERR(smsm->state)) {
 544                dev_err(smsm->dev, "failed to register qcom_smem_state\n");
 545                return PTR_ERR(smsm->state);
 546        }
 547
 548        /* Register handlers for remote processor entries of interest. */
 549        for_each_available_child_of_node(pdev->dev.of_node, node) {
 550                if (!of_property_read_bool(node, "interrupt-controller"))
 551                        continue;
 552
 553                ret = of_property_read_u32(node, "reg", &id);
 554                if (ret || id >= smsm->num_entries) {
 555                        dev_err(&pdev->dev, "invalid reg of entry\n");
 556                        if (!ret)
 557                                ret = -EINVAL;
 558                        goto unwind_interfaces;
 559                }
 560                entry = &smsm->entries[id];
 561
 562                entry->smsm = smsm;
 563                entry->remote_state = states + id;
 564
 565                /* Setup subscription pointers and unsubscribe to any kicks */
 566                entry->subscription = intr_mask + id * smsm->num_hosts;
 567                writel(0, entry->subscription + smsm->local_host);
 568
 569                ret = smsm_inbound_entry(smsm, entry, node);
 570                if (ret < 0)
 571                        goto unwind_interfaces;
 572        }
 573
 574        platform_set_drvdata(pdev, smsm);
 575
 576        return 0;
 577
 578unwind_interfaces:
 579        for (id = 0; id < smsm->num_entries; id++)
 580                if (smsm->entries[id].domain)
 581                        irq_domain_remove(smsm->entries[id].domain);
 582
 583        qcom_smem_state_unregister(smsm->state);
 584
 585        return ret;
 586}
 587
 588static int qcom_smsm_remove(struct platform_device *pdev)
 589{
 590        struct qcom_smsm *smsm = platform_get_drvdata(pdev);
 591        unsigned id;
 592
 593        for (id = 0; id < smsm->num_entries; id++)
 594                if (smsm->entries[id].domain)
 595                        irq_domain_remove(smsm->entries[id].domain);
 596
 597        qcom_smem_state_unregister(smsm->state);
 598
 599        return 0;
 600}
 601
 602static const struct of_device_id qcom_smsm_of_match[] = {
 603        { .compatible = "qcom,smsm" },
 604        {}
 605};
 606MODULE_DEVICE_TABLE(of, qcom_smsm_of_match);
 607
 608static struct platform_driver qcom_smsm_driver = {
 609        .probe = qcom_smsm_probe,
 610        .remove = qcom_smsm_remove,
 611        .driver  = {
 612                .name  = "qcom-smsm",
 613                .of_match_table = qcom_smsm_of_match,
 614        },
 615};
 616module_platform_driver(qcom_smsm_driver);
 617
 618MODULE_DESCRIPTION("Qualcomm Shared Memory State Machine driver");
 619MODULE_LICENSE("GPL v2");
 620