|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | + |
| 3 | +/* Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. |
| 4 | + * Copyright (C) 2018-2020 Linaro Ltd. |
| 5 | + */ |
| 6 | + |
| 7 | +#include <linux/atomic.h> |
| 8 | +#include <linux/mutex.h> |
| 9 | +#include <linux/clk.h> |
| 10 | +#include <linux/device.h> |
| 11 | +#include <linux/interconnect.h> |
| 12 | + |
| 13 | +#include "ipa.h" |
| 14 | +#include "ipa_clock.h" |
| 15 | +#include "ipa_modem.h" |
| 16 | + |
| 17 | +/** |
| 18 | + * DOC: IPA Clocking |
| 19 | + * |
| 20 | + * The "IPA Clock" manages both the IPA core clock and the interconnects |
| 21 | + * (buses) the IPA depends on as a single logical entity. A reference count |
| 22 | + * is incremented by "get" operations and decremented by "put" operations. |
| 23 | + * Transitions of that count from 0 to 1 result in the clock and interconnects |
| 24 | + * being enabled, and transitions of the count from 1 to 0 cause them to be |
| 25 | + * disabled. We currently operate the core clock at a fixed clock rate, and |
| 26 | + * all buses at a fixed average and peak bandwidth. As more advanced IPA |
| 27 | + * features are enabled, we can make better use of clock and bus scaling. |
| 28 | + * |
| 29 | + * An IPA clock reference must be held for any access to IPA hardware. |
| 30 | + */ |
| 31 | + |
| 32 | +#define IPA_CORE_CLOCK_RATE (75UL * 1000 * 1000) /* Hz */ |
| 33 | + |
| 34 | +/* Interconnect path bandwidths (each times 1000 bytes per second) */ |
| 35 | +#define IPA_MEMORY_AVG (80 * 1000) /* 80 MBps */ |
| 36 | +#define IPA_MEMORY_PEAK (600 * 1000) |
| 37 | + |
| 38 | +#define IPA_IMEM_AVG (80 * 1000) |
| 39 | +#define IPA_IMEM_PEAK (350 * 1000) |
| 40 | + |
| 41 | +#define IPA_CONFIG_AVG (40 * 1000) |
| 42 | +#define IPA_CONFIG_PEAK (40 * 1000) |
| 43 | + |
| 44 | +/** |
| 45 | + * struct ipa_clock - IPA clocking information |
| 46 | + * @count: Clocking reference count |
| 47 | + * @mutex; Protects clock enable/disable |
| 48 | + * @core: IPA core clock |
| 49 | + * @memory_path: Memory interconnect |
| 50 | + * @imem_path: Internal memory interconnect |
| 51 | + * @config_path: Configuration space interconnect |
| 52 | + */ |
| 53 | +struct ipa_clock { |
| 54 | + atomic_t count; |
| 55 | + struct mutex mutex; /* protects clock enable/disable */ |
| 56 | + struct clk *core; |
| 57 | + struct icc_path *memory_path; |
| 58 | + struct icc_path *imem_path; |
| 59 | + struct icc_path *config_path; |
| 60 | +}; |
| 61 | + |
| 62 | +static struct icc_path * |
| 63 | +ipa_interconnect_init_one(struct device *dev, const char *name) |
| 64 | +{ |
| 65 | + struct icc_path *path; |
| 66 | + |
| 67 | + path = of_icc_get(dev, name); |
| 68 | + if (IS_ERR(path)) |
| 69 | + dev_err(dev, "error %ld getting memory interconnect\n", |
| 70 | + PTR_ERR(path)); |
| 71 | + |
| 72 | + return path; |
| 73 | +} |
| 74 | + |
| 75 | +/* Initialize interconnects required for IPA operation */ |
| 76 | +static int ipa_interconnect_init(struct ipa_clock *clock, struct device *dev) |
| 77 | +{ |
| 78 | + struct icc_path *path; |
| 79 | + |
| 80 | + path = ipa_interconnect_init_one(dev, "memory"); |
| 81 | + if (IS_ERR(path)) |
| 82 | + goto err_return; |
| 83 | + clock->memory_path = path; |
| 84 | + |
| 85 | + path = ipa_interconnect_init_one(dev, "imem"); |
| 86 | + if (IS_ERR(path)) |
| 87 | + goto err_memory_path_put; |
| 88 | + clock->imem_path = path; |
| 89 | + |
| 90 | + path = ipa_interconnect_init_one(dev, "config"); |
| 91 | + if (IS_ERR(path)) |
| 92 | + goto err_imem_path_put; |
| 93 | + clock->config_path = path; |
| 94 | + |
| 95 | + return 0; |
| 96 | + |
| 97 | +err_imem_path_put: |
| 98 | + icc_put(clock->imem_path); |
| 99 | +err_memory_path_put: |
| 100 | + icc_put(clock->memory_path); |
| 101 | +err_return: |
| 102 | + return PTR_ERR(path); |
| 103 | +} |
| 104 | + |
| 105 | +/* Inverse of ipa_interconnect_init() */ |
| 106 | +static void ipa_interconnect_exit(struct ipa_clock *clock) |
| 107 | +{ |
| 108 | + icc_put(clock->config_path); |
| 109 | + icc_put(clock->imem_path); |
| 110 | + icc_put(clock->memory_path); |
| 111 | +} |
| 112 | + |
| 113 | +/* Currently we only use one bandwidth level, so just "enable" interconnects */ |
| 114 | +static int ipa_interconnect_enable(struct ipa *ipa) |
| 115 | +{ |
| 116 | + struct ipa_clock *clock = ipa->clock; |
| 117 | + int ret; |
| 118 | + |
| 119 | + ret = icc_set_bw(clock->memory_path, IPA_MEMORY_AVG, IPA_MEMORY_PEAK); |
| 120 | + if (ret) |
| 121 | + return ret; |
| 122 | + |
| 123 | + ret = icc_set_bw(clock->imem_path, IPA_IMEM_AVG, IPA_IMEM_PEAK); |
| 124 | + if (ret) |
| 125 | + goto err_memory_path_disable; |
| 126 | + |
| 127 | + ret = icc_set_bw(clock->config_path, IPA_CONFIG_AVG, IPA_CONFIG_PEAK); |
| 128 | + if (ret) |
| 129 | + goto err_imem_path_disable; |
| 130 | + |
| 131 | + return 0; |
| 132 | + |
| 133 | +err_imem_path_disable: |
| 134 | + (void)icc_set_bw(clock->imem_path, 0, 0); |
| 135 | +err_memory_path_disable: |
| 136 | + (void)icc_set_bw(clock->memory_path, 0, 0); |
| 137 | + |
| 138 | + return ret; |
| 139 | +} |
| 140 | + |
| 141 | +/* To disable an interconnect, we just its bandwidth to 0 */ |
| 142 | +static int ipa_interconnect_disable(struct ipa *ipa) |
| 143 | +{ |
| 144 | + struct ipa_clock *clock = ipa->clock; |
| 145 | + int ret; |
| 146 | + |
| 147 | + ret = icc_set_bw(clock->memory_path, 0, 0); |
| 148 | + if (ret) |
| 149 | + return ret; |
| 150 | + |
| 151 | + ret = icc_set_bw(clock->imem_path, 0, 0); |
| 152 | + if (ret) |
| 153 | + goto err_memory_path_reenable; |
| 154 | + |
| 155 | + ret = icc_set_bw(clock->config_path, 0, 0); |
| 156 | + if (ret) |
| 157 | + goto err_imem_path_reenable; |
| 158 | + |
| 159 | + return 0; |
| 160 | + |
| 161 | +err_imem_path_reenable: |
| 162 | + (void)icc_set_bw(clock->imem_path, IPA_IMEM_AVG, IPA_IMEM_PEAK); |
| 163 | +err_memory_path_reenable: |
| 164 | + (void)icc_set_bw(clock->memory_path, IPA_MEMORY_AVG, IPA_MEMORY_PEAK); |
| 165 | + |
| 166 | + return ret; |
| 167 | +} |
| 168 | + |
| 169 | +/* Turn on IPA clocks, including interconnects */ |
| 170 | +static int ipa_clock_enable(struct ipa *ipa) |
| 171 | +{ |
| 172 | + int ret; |
| 173 | + |
| 174 | + ret = ipa_interconnect_enable(ipa); |
| 175 | + if (ret) |
| 176 | + return ret; |
| 177 | + |
| 178 | + ret = clk_prepare_enable(ipa->clock->core); |
| 179 | + if (ret) |
| 180 | + ipa_interconnect_disable(ipa); |
| 181 | + |
| 182 | + return ret; |
| 183 | +} |
| 184 | + |
| 185 | +/* Inverse of ipa_clock_enable() */ |
| 186 | +static void ipa_clock_disable(struct ipa *ipa) |
| 187 | +{ |
| 188 | + clk_disable_unprepare(ipa->clock->core); |
| 189 | + (void)ipa_interconnect_disable(ipa); |
| 190 | +} |
| 191 | + |
| 192 | +/* Get an IPA clock reference, but only if the reference count is |
| 193 | + * already non-zero. Returns true if the additional reference was |
| 194 | + * added successfully, or false otherwise. |
| 195 | + */ |
| 196 | +bool ipa_clock_get_additional(struct ipa *ipa) |
| 197 | +{ |
| 198 | + return !!atomic_inc_not_zero(&ipa->clock->count); |
| 199 | +} |
| 200 | + |
| 201 | +/* Get an IPA clock reference. If the reference count is non-zero, it is |
| 202 | + * incremented and return is immediate. Otherwise it is checked again |
| 203 | + * under protection of the mutex, and if appropriate the clock (and |
| 204 | + * interconnects) are enabled suspended endpoints (if any) are resumed |
| 205 | + * before returning. |
| 206 | + * |
| 207 | + * Incrementing the reference count is intentionally deferred until |
| 208 | + * after the clock is running and endpoints are resumed. |
| 209 | + */ |
| 210 | +void ipa_clock_get(struct ipa *ipa) |
| 211 | +{ |
| 212 | + struct ipa_clock *clock = ipa->clock; |
| 213 | + int ret; |
| 214 | + |
| 215 | + /* If the clock is running, just bump the reference count */ |
| 216 | + if (ipa_clock_get_additional(ipa)) |
| 217 | + return; |
| 218 | + |
| 219 | + /* Otherwise get the mutex and check again */ |
| 220 | + mutex_lock(&clock->mutex); |
| 221 | + |
| 222 | + /* A reference might have been added before we got the mutex. */ |
| 223 | + if (ipa_clock_get_additional(ipa)) |
| 224 | + goto out_mutex_unlock; |
| 225 | + |
| 226 | + ret = ipa_clock_enable(ipa); |
| 227 | + if (ret) { |
| 228 | + dev_err(&ipa->pdev->dev, "error %d enabling IPA clock\n", ret); |
| 229 | + goto out_mutex_unlock; |
| 230 | + } |
| 231 | + |
| 232 | + ipa_endpoint_resume(ipa); |
| 233 | + |
| 234 | + atomic_inc(&clock->count); |
| 235 | + |
| 236 | +out_mutex_unlock: |
| 237 | + mutex_unlock(&clock->mutex); |
| 238 | +} |
| 239 | + |
| 240 | +/* Attempt to remove an IPA clock reference. If this represents the last |
| 241 | + * reference, suspend endpoints and disable the clock (and interconnects) |
| 242 | + * under protection of a mutex. |
| 243 | + */ |
| 244 | +void ipa_clock_put(struct ipa *ipa) |
| 245 | +{ |
| 246 | + struct ipa_clock *clock = ipa->clock; |
| 247 | + |
| 248 | + /* If this is not the last reference there's nothing more to do */ |
| 249 | + if (!atomic_dec_and_mutex_lock(&clock->count, &clock->mutex)) |
| 250 | + return; |
| 251 | + |
| 252 | + ipa_endpoint_suspend(ipa); |
| 253 | + |
| 254 | + ipa_clock_disable(ipa); |
| 255 | + |
| 256 | + mutex_unlock(&clock->mutex); |
| 257 | +} |
| 258 | + |
| 259 | +/* Initialize IPA clocking */ |
| 260 | +struct ipa_clock *ipa_clock_init(struct device *dev) |
| 261 | +{ |
| 262 | + struct ipa_clock *clock; |
| 263 | + struct clk *clk; |
| 264 | + int ret; |
| 265 | + |
| 266 | + clk = clk_get(dev, "core"); |
| 267 | + if (IS_ERR(clk)) { |
| 268 | + dev_err(dev, "error %ld getting core clock\n", PTR_ERR(clk)); |
| 269 | + return ERR_CAST(clk); |
| 270 | + } |
| 271 | + |
| 272 | + ret = clk_set_rate(clk, IPA_CORE_CLOCK_RATE); |
| 273 | + if (ret) { |
| 274 | + dev_err(dev, "error %d setting core clock rate to %lu\n", |
| 275 | + ret, IPA_CORE_CLOCK_RATE); |
| 276 | + goto err_clk_put; |
| 277 | + } |
| 278 | + |
| 279 | + clock = kzalloc(sizeof(*clock), GFP_KERNEL); |
| 280 | + if (!clock) { |
| 281 | + ret = -ENOMEM; |
| 282 | + goto err_clk_put; |
| 283 | + } |
| 284 | + clock->core = clk; |
| 285 | + |
| 286 | + ret = ipa_interconnect_init(clock, dev); |
| 287 | + if (ret) |
| 288 | + goto err_kfree; |
| 289 | + |
| 290 | + mutex_init(&clock->mutex); |
| 291 | + atomic_set(&clock->count, 0); |
| 292 | + |
| 293 | + return clock; |
| 294 | + |
| 295 | +err_kfree: |
| 296 | + kfree(clock); |
| 297 | +err_clk_put: |
| 298 | + clk_put(clk); |
| 299 | + |
| 300 | + return ERR_PTR(ret); |
| 301 | +} |
| 302 | + |
| 303 | +/* Inverse of ipa_clock_init() */ |
| 304 | +void ipa_clock_exit(struct ipa_clock *clock) |
| 305 | +{ |
| 306 | + struct clk *clk = clock->core; |
| 307 | + |
| 308 | + WARN_ON(atomic_read(&clock->count) != 0); |
| 309 | + mutex_destroy(&clock->mutex); |
| 310 | + ipa_interconnect_exit(clock); |
| 311 | + kfree(clock); |
| 312 | + clk_put(clk); |
| 313 | +} |
0 commit comments