linux/drivers/interconnect/qcom/bcm-voter.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
   4 */
   5
   6#include <asm/div64.h>
   7#include <linux/interconnect-provider.h>
   8#include <linux/list_sort.h>
   9#include <linux/module.h>
  10#include <linux/of.h>
  11#include <linux/platform_device.h>
  12
  13#include <soc/qcom/rpmh.h>
  14#include <soc/qcom/tcs.h>
  15
  16#include "bcm-voter.h"
  17#include "icc-rpmh.h"
  18
  19static LIST_HEAD(bcm_voters);
  20static DEFINE_MUTEX(bcm_voter_lock);
  21
  22/**
  23 * struct bcm_voter - Bus Clock Manager voter
  24 * @dev: reference to the device that communicates with the BCM
  25 * @np: reference to the device node to match bcm voters
  26 * @lock: mutex to protect commit and wake/sleep lists in the voter
  27 * @commit_list: list containing bcms to be committed to hardware
  28 * @ws_list: list containing bcms that have different wake/sleep votes
  29 * @voter_node: list of bcm voters
  30 * @tcs_wait: mask for which buckets require TCS completion
  31 */
  32struct bcm_voter {
  33        struct device *dev;
  34        struct device_node *np;
  35        struct mutex lock;
  36        struct list_head commit_list;
  37        struct list_head ws_list;
  38        struct list_head voter_node;
  39        u32 tcs_wait;
  40};
  41
  42static int cmp_vcd(void *priv, const struct list_head *a, const struct list_head *b)
  43{
  44        const struct qcom_icc_bcm *bcm_a = list_entry(a, struct qcom_icc_bcm, list);
  45        const struct qcom_icc_bcm *bcm_b = list_entry(b, struct qcom_icc_bcm, list);
  46
  47        return bcm_a->aux_data.vcd - bcm_b->aux_data.vcd;
  48}
  49
  50static u64 bcm_div(u64 num, u32 base)
  51{
  52        /* Ensure that small votes aren't lost. */
  53        if (num && num < base)
  54                return 1;
  55
  56        do_div(num, base);
  57
  58        return num;
  59}
  60
  61static void bcm_aggregate(struct qcom_icc_bcm *bcm)
  62{
  63        struct qcom_icc_node *node;
  64        size_t i, bucket;
  65        u64 agg_avg[QCOM_ICC_NUM_BUCKETS] = {0};
  66        u64 agg_peak[QCOM_ICC_NUM_BUCKETS] = {0};
  67        u64 temp;
  68
  69        for (bucket = 0; bucket < QCOM_ICC_NUM_BUCKETS; bucket++) {
  70                for (i = 0; i < bcm->num_nodes; i++) {
  71                        node = bcm->nodes[i];
  72                        temp = bcm_div(node->sum_avg[bucket] * bcm->aux_data.width,
  73                                       node->buswidth * node->channels);
  74                        agg_avg[bucket] = max(agg_avg[bucket], temp);
  75
  76                        temp = bcm_div(node->max_peak[bucket] * bcm->aux_data.width,
  77                                       node->buswidth);
  78                        agg_peak[bucket] = max(agg_peak[bucket], temp);
  79                }
  80
  81                temp = agg_avg[bucket] * bcm->vote_scale;
  82                bcm->vote_x[bucket] = bcm_div(temp, bcm->aux_data.unit);
  83
  84                temp = agg_peak[bucket] * bcm->vote_scale;
  85                bcm->vote_y[bucket] = bcm_div(temp, bcm->aux_data.unit);
  86        }
  87
  88        if (bcm->keepalive && bcm->vote_x[QCOM_ICC_BUCKET_AMC] == 0 &&
  89            bcm->vote_y[QCOM_ICC_BUCKET_AMC] == 0) {
  90                bcm->vote_x[QCOM_ICC_BUCKET_AMC] = 1;
  91                bcm->vote_x[QCOM_ICC_BUCKET_WAKE] = 1;
  92                bcm->vote_y[QCOM_ICC_BUCKET_AMC] = 1;
  93                bcm->vote_y[QCOM_ICC_BUCKET_WAKE] = 1;
  94        }
  95}
  96
  97static inline void tcs_cmd_gen(struct tcs_cmd *cmd, u64 vote_x, u64 vote_y,
  98                               u32 addr, bool commit, bool wait)
  99{
 100        bool valid = true;
 101
 102        if (!cmd)
 103                return;
 104
 105        memset(cmd, 0, sizeof(*cmd));
 106
 107        if (vote_x == 0 && vote_y == 0)
 108                valid = false;
 109
 110        if (vote_x > BCM_TCS_CMD_VOTE_MASK)
 111                vote_x = BCM_TCS_CMD_VOTE_MASK;
 112
 113        if (vote_y > BCM_TCS_CMD_VOTE_MASK)
 114                vote_y = BCM_TCS_CMD_VOTE_MASK;
 115
 116        cmd->addr = addr;
 117        cmd->data = BCM_TCS_CMD(commit, valid, vote_x, vote_y);
 118
 119        /*
 120         * Set the wait for completion flag on command that need to be completed
 121         * before the next command.
 122         */
 123        cmd->wait = wait;
 124}
 125
 126static void tcs_list_gen(struct bcm_voter *voter, int bucket,
 127                         struct tcs_cmd tcs_list[MAX_VCD],
 128                         int n[MAX_VCD + 1])
 129{
 130        struct list_head *bcm_list = &voter->commit_list;
 131        struct qcom_icc_bcm *bcm;
 132        bool commit, wait;
 133        size_t idx = 0, batch = 0, cur_vcd_size = 0;
 134
 135        memset(n, 0, sizeof(int) * (MAX_VCD + 1));
 136
 137        list_for_each_entry(bcm, bcm_list, list) {
 138                commit = false;
 139                cur_vcd_size++;
 140                if ((list_is_last(&bcm->list, bcm_list)) ||
 141                    bcm->aux_data.vcd != list_next_entry(bcm, list)->aux_data.vcd) {
 142                        commit = true;
 143                        cur_vcd_size = 0;
 144                }
 145
 146                wait = commit && (voter->tcs_wait & BIT(bucket));
 147
 148                tcs_cmd_gen(&tcs_list[idx], bcm->vote_x[bucket],
 149                            bcm->vote_y[bucket], bcm->addr, commit, wait);
 150                idx++;
 151                n[batch]++;
 152                /*
 153                 * Batch the BCMs in such a way that we do not split them in
 154                 * multiple payloads when they are under the same VCD. This is
 155                 * to ensure that every BCM is committed since we only set the
 156                 * commit bit on the last BCM request of every VCD.
 157                 */
 158                if (n[batch] >= MAX_RPMH_PAYLOAD) {
 159                        if (!commit) {
 160                                n[batch] -= cur_vcd_size;
 161                                n[batch + 1] = cur_vcd_size;
 162                        }
 163                        batch++;
 164                }
 165        }
 166}
 167
 168/**
 169 * of_bcm_voter_get - gets a bcm voter handle from DT node
 170 * @dev: device pointer for the consumer device
 171 * @name: name for the bcm voter device
 172 *
 173 * This function will match a device_node pointer for the phandle
 174 * specified in the device DT and return a bcm_voter handle on success.
 175 *
 176 * Returns bcm_voter pointer or ERR_PTR() on error. EPROBE_DEFER is returned
 177 * when matching bcm voter is yet to be found.
 178 */
 179struct bcm_voter *of_bcm_voter_get(struct device *dev, const char *name)
 180{
 181        struct bcm_voter *voter = ERR_PTR(-EPROBE_DEFER);
 182        struct bcm_voter *temp;
 183        struct device_node *np, *node;
 184        int idx = 0;
 185
 186        if (!dev || !dev->of_node)
 187                return ERR_PTR(-ENODEV);
 188
 189        np = dev->of_node;
 190
 191        if (name) {
 192                idx = of_property_match_string(np, "qcom,bcm-voter-names", name);
 193                if (idx < 0)
 194                        return ERR_PTR(idx);
 195        }
 196
 197        node = of_parse_phandle(np, "qcom,bcm-voters", idx);
 198
 199        mutex_lock(&bcm_voter_lock);
 200        list_for_each_entry(temp, &bcm_voters, voter_node) {
 201                if (temp->np == node) {
 202                        voter = temp;
 203                        break;
 204                }
 205        }
 206        mutex_unlock(&bcm_voter_lock);
 207
 208        of_node_put(node);
 209        return voter;
 210}
 211EXPORT_SYMBOL_GPL(of_bcm_voter_get);
 212
 213/**
 214 * qcom_icc_bcm_voter_add - queues up the bcm nodes that require updates
 215 * @voter: voter that the bcms are being added to
 216 * @bcm: bcm to add to the commit and wake sleep list
 217 */
 218void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm)
 219{
 220        if (!voter)
 221                return;
 222
 223        mutex_lock(&voter->lock);
 224        if (list_empty(&bcm->list))
 225                list_add_tail(&bcm->list, &voter->commit_list);
 226
 227        if (list_empty(&bcm->ws_list))
 228                list_add_tail(&bcm->ws_list, &voter->ws_list);
 229
 230        mutex_unlock(&voter->lock);
 231}
 232EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_add);
 233
 234/**
 235 * qcom_icc_bcm_voter_commit - generates and commits tcs cmds based on bcms
 236 * @voter: voter that needs flushing
 237 *
 238 * This function generates a set of AMC commands and flushes to the BCM device
 239 * associated with the voter. It conditionally generate WAKE and SLEEP commands
 240 * based on deltas between WAKE/SLEEP requirements. The ws_list persists
 241 * through multiple commit requests and bcm nodes are removed only when the
 242 * requirements for WAKE matches SLEEP.
 243 *
 244 * Returns 0 on success, or an appropriate error code otherwise.
 245 */
 246int qcom_icc_bcm_voter_commit(struct bcm_voter *voter)
 247{
 248        struct qcom_icc_bcm *bcm;
 249        struct qcom_icc_bcm *bcm_tmp;
 250        int commit_idx[MAX_VCD + 1];
 251        struct tcs_cmd cmds[MAX_BCMS];
 252        int ret = 0;
 253
 254        if (!voter)
 255                return 0;
 256
 257        mutex_lock(&voter->lock);
 258        list_for_each_entry(bcm, &voter->commit_list, list)
 259                bcm_aggregate(bcm);
 260
 261        /*
 262         * Pre sort the BCMs based on VCD for ease of generating a command list
 263         * that groups the BCMs with the same VCD together. VCDs are numbered
 264         * with lowest being the most expensive time wise, ensuring that
 265         * those commands are being sent the earliest in the queue. This needs
 266         * to be sorted every commit since we can't guarantee the order in which
 267         * the BCMs are added to the list.
 268         */
 269        list_sort(NULL, &voter->commit_list, cmp_vcd);
 270
 271        /*
 272         * Construct the command list based on a pre ordered list of BCMs
 273         * based on VCD.
 274         */
 275        tcs_list_gen(voter, QCOM_ICC_BUCKET_AMC, cmds, commit_idx);
 276        if (!commit_idx[0])
 277                goto out;
 278
 279        rpmh_invalidate(voter->dev);
 280
 281        ret = rpmh_write_batch(voter->dev, RPMH_ACTIVE_ONLY_STATE,
 282                               cmds, commit_idx);
 283        if (ret) {
 284                pr_err("Error sending AMC RPMH requests (%d)\n", ret);
 285                goto out;
 286        }
 287
 288        list_for_each_entry_safe(bcm, bcm_tmp, &voter->commit_list, list)
 289                list_del_init(&bcm->list);
 290
 291        list_for_each_entry_safe(bcm, bcm_tmp, &voter->ws_list, ws_list) {
 292                /*
 293                 * Only generate WAKE and SLEEP commands if a resource's
 294                 * requirements change as the execution environment transitions
 295                 * between different power states.
 296                 */
 297                if (bcm->vote_x[QCOM_ICC_BUCKET_WAKE] !=
 298                    bcm->vote_x[QCOM_ICC_BUCKET_SLEEP] ||
 299                    bcm->vote_y[QCOM_ICC_BUCKET_WAKE] !=
 300                    bcm->vote_y[QCOM_ICC_BUCKET_SLEEP])
 301                        list_add_tail(&bcm->list, &voter->commit_list);
 302                else
 303                        list_del_init(&bcm->ws_list);
 304        }
 305
 306        if (list_empty(&voter->commit_list))
 307                goto out;
 308
 309        list_sort(NULL, &voter->commit_list, cmp_vcd);
 310
 311        tcs_list_gen(voter, QCOM_ICC_BUCKET_WAKE, cmds, commit_idx);
 312
 313        ret = rpmh_write_batch(voter->dev, RPMH_WAKE_ONLY_STATE, cmds, commit_idx);
 314        if (ret) {
 315                pr_err("Error sending WAKE RPMH requests (%d)\n", ret);
 316                goto out;
 317        }
 318
 319        tcs_list_gen(voter, QCOM_ICC_BUCKET_SLEEP, cmds, commit_idx);
 320
 321        ret = rpmh_write_batch(voter->dev, RPMH_SLEEP_STATE, cmds, commit_idx);
 322        if (ret) {
 323                pr_err("Error sending SLEEP RPMH requests (%d)\n", ret);
 324                goto out;
 325        }
 326
 327out:
 328        list_for_each_entry_safe(bcm, bcm_tmp, &voter->commit_list, list)
 329                list_del_init(&bcm->list);
 330
 331        mutex_unlock(&voter->lock);
 332        return ret;
 333}
 334EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_commit);
 335
 336static int qcom_icc_bcm_voter_probe(struct platform_device *pdev)
 337{
 338        struct device_node *np = pdev->dev.of_node;
 339        struct bcm_voter *voter;
 340
 341        voter = devm_kzalloc(&pdev->dev, sizeof(*voter), GFP_KERNEL);
 342        if (!voter)
 343                return -ENOMEM;
 344
 345        voter->dev = &pdev->dev;
 346        voter->np = np;
 347
 348        if (of_property_read_u32(np, "qcom,tcs-wait", &voter->tcs_wait))
 349                voter->tcs_wait = QCOM_ICC_TAG_ACTIVE_ONLY;
 350
 351        mutex_init(&voter->lock);
 352        INIT_LIST_HEAD(&voter->commit_list);
 353        INIT_LIST_HEAD(&voter->ws_list);
 354
 355        mutex_lock(&bcm_voter_lock);
 356        list_add_tail(&voter->voter_node, &bcm_voters);
 357        mutex_unlock(&bcm_voter_lock);
 358
 359        return 0;
 360}
 361
 362static const struct of_device_id bcm_voter_of_match[] = {
 363        { .compatible = "qcom,bcm-voter" },
 364        { }
 365};
 366MODULE_DEVICE_TABLE(of, bcm_voter_of_match);
 367
 368static struct platform_driver qcom_icc_bcm_voter_driver = {
 369        .probe = qcom_icc_bcm_voter_probe,
 370        .driver = {
 371                .name           = "bcm_voter",
 372                .of_match_table = bcm_voter_of_match,
 373        },
 374};
 375module_platform_driver(qcom_icc_bcm_voter_driver);
 376
 377MODULE_AUTHOR("David Dai <daidavid1@codeaurora.org>");
 378MODULE_DESCRIPTION("Qualcomm BCM Voter interconnect driver");
 379MODULE_LICENSE("GPL v2");
 380