| 1 | |
| 2 | |
| 3 | * Apple Silicon hardware tunable support
|
| 4 | *
|
| 5 | * Each tunable is a list with each entry containing a offset into the MMIO
|
| 6 | * region, a mask of bits to be cleared and a set of bits to be set. These
|
| 7 | * tunables are passed along by the previous boot stages and vary from device
|
| 8 | * to device such that they cannot be hardcoded in the individual drivers.
|
| 9 | *
|
| 10 | * Copyright (C) The Asahi Linux Contributors
|
| 11 |
|
| 12 |
|
| 13 | #include <linux/io.h>
|
| 14 | #include <linux/module.h>
|
| 15 | #include <linux/of.h>
|
| 16 | #include <linux/overflow.h>
|
| 17 | #include <linux/soc/apple/tunable.h>
|
| 18 |
|
| 19 | struct apple_tunable *devm_apple_tunable_parse(struct device *dev,
|
| 20 | struct device_node *np,
|
| 21 | const char *name,
|
| 22 | struct resource *res)
|
| 23 | {
|
| 24 | struct apple_tunable *tunable;
|
| 25 | struct property *prop;
|
| 26 | const __be32 *p;
|
| 27 | size_t sz;
|
| 28 | int i;
|
| 29 |
|
| 30 | if (resource_size(res) < 4)
|
| 31 | return ERR_PTR(-EINVAL);
|
| 32 |
|
| 33 | prop = of_find_property(np, name, NULL);
|
| 34 | if (!prop)
|
| 35 | return ERR_PTR(-ENOENT);
|
| 36 |
|
| 37 | if (prop->length % (3 * sizeof(u32)))
|
| 38 | return ERR_PTR(-EINVAL);
|
| 39 | sz = prop->length / (3 * sizeof(u32));
|
| 40 |
|
| 41 | tunable = devm_kzalloc(dev, struct_size(tunable, values, sz), GFP_KERNEL);
|
| 42 | if (!tunable)
|
| 43 | return ERR_PTR(-ENOMEM);
|
| 44 | tunable->sz = sz;
|
| 45 |
|
| 46 | for (i = 0, p = NULL; i < tunable->sz; ++i) {
|
| 47 | p = of_prop_next_u32(prop, p, &tunable->values[i].offset);
|
| 48 | p = of_prop_next_u32(prop, p, &tunable->values[i].mask);
|
| 49 | p = of_prop_next_u32(prop, p, &tunable->values[i].value);
|
| 50 |
|
| 51 |
|
| 52 | if (tunable->values[i].offset % 4)
|
| 53 | return ERR_PTR(-EINVAL);
|
| 54 | if (tunable->values[i].offset > (resource_size(res) - 4))
|
| 55 | return ERR_PTR(-EINVAL);
|
| 56 | }
|
| 57 |
|
| 58 | return tunable;
|
| 59 | }
|
| 60 | EXPORT_SYMBOL(devm_apple_tunable_parse);
|
| 61 |
|
| 62 | void apple_tunable_apply(void __iomem *regs, struct apple_tunable *tunable)
|
| 63 | {
|
| 64 | size_t i;
|
| 65 |
|
| 66 | for (i = 0; i < tunable->sz; ++i) {
|
| 67 | u32 val, old_val;
|
| 68 |
|
| 69 | old_val = readl(regs + tunable->values[i].offset);
|
| 70 | val = old_val & ~tunable->values[i].mask;
|
| 71 | val |= tunable->values[i].value;
|
| 72 | if (val != old_val)
|
| 73 | writel(val, regs + tunable->values[i].offset);
|
| 74 | }
|
| 75 | }
|
| 76 | EXPORT_SYMBOL(apple_tunable_apply);
|
| 77 |
|
| 78 | MODULE_LICENSE("Dual MIT/GPL");
|
| 79 | MODULE_AUTHOR("Sven Peter <[email protected]>");
|
| 80 | MODULE_DESCRIPTION("Apple Silicon hardware tunable support");
|