Commit | Line | Data |
---|---|---|
8ba01cae | 1 | // SPDX-License-Identifier: GPL-2.0 |
19a0f612 BA |
2 | /* |
3 | * Copyright (c) 2013, The Linux Foundation. All rights reserved. | |
4 | * Copyright (c) 2015, Sony Mobile Communications AB | |
19a0f612 BA |
5 | */ |
6 | ||
7 | #include <linux/hwspinlock.h> | |
8 | #include <linux/io.h> | |
9 | #include <linux/kernel.h> | |
10 | #include <linux/mfd/syscon.h> | |
11 | #include <linux/module.h> | |
12 | #include <linux/of.h> | |
13 | #include <linux/of_device.h> | |
14 | #include <linux/platform_device.h> | |
19a0f612 BA |
15 | #include <linux/regmap.h> |
16 | ||
17 | #include "hwspinlock_internal.h" | |
18 | ||
19 | #define QCOM_MUTEX_APPS_PROC_ID 1 | |
20 | #define QCOM_MUTEX_NUM_LOCKS 32 | |
21 | ||
22 | static int qcom_hwspinlock_trylock(struct hwspinlock *lock) | |
23 | { | |
24 | struct regmap_field *field = lock->priv; | |
25 | u32 lock_owner; | |
26 | int ret; | |
27 | ||
28 | ret = regmap_field_write(field, QCOM_MUTEX_APPS_PROC_ID); | |
29 | if (ret) | |
30 | return ret; | |
31 | ||
32 | ret = regmap_field_read(field, &lock_owner); | |
33 | if (ret) | |
34 | return ret; | |
35 | ||
36 | return lock_owner == QCOM_MUTEX_APPS_PROC_ID; | |
37 | } | |
38 | ||
39 | static void qcom_hwspinlock_unlock(struct hwspinlock *lock) | |
40 | { | |
41 | struct regmap_field *field = lock->priv; | |
42 | u32 lock_owner; | |
43 | int ret; | |
44 | ||
45 | ret = regmap_field_read(field, &lock_owner); | |
46 | if (ret) { | |
47 | pr_err("%s: unable to query spinlock owner\n", __func__); | |
48 | return; | |
49 | } | |
50 | ||
51 | if (lock_owner != QCOM_MUTEX_APPS_PROC_ID) { | |
52 | pr_err("%s: spinlock not owned by us (actual owner is %d)\n", | |
53 | __func__, lock_owner); | |
54 | } | |
55 | ||
56 | ret = regmap_field_write(field, 0); | |
57 | if (ret) | |
58 | pr_err("%s: failed to unlock spinlock\n", __func__); | |
59 | } | |
60 | ||
61 | static const struct hwspinlock_ops qcom_hwspinlock_ops = { | |
62 | .trylock = qcom_hwspinlock_trylock, | |
63 | .unlock = qcom_hwspinlock_unlock, | |
64 | }; | |
65 | ||
66 | static const struct of_device_id qcom_hwspinlock_of_match[] = { | |
67 | { .compatible = "qcom,sfpb-mutex" }, | |
68 | { .compatible = "qcom,tcsr-mutex" }, | |
69 | { } | |
70 | }; | |
71 | MODULE_DEVICE_TABLE(of, qcom_hwspinlock_of_match); | |
72 | ||
7a1e6fb1 BA |
73 | static struct regmap *qcom_hwspinlock_probe_syscon(struct platform_device *pdev, |
74 | u32 *base, u32 *stride) | |
19a0f612 | 75 | { |
19a0f612 | 76 | struct device_node *syscon; |
19a0f612 | 77 | struct regmap *regmap; |
19a0f612 | 78 | int ret; |
19a0f612 BA |
79 | |
80 | syscon = of_parse_phandle(pdev->dev.of_node, "syscon", 0); | |
7a1e6fb1 BA |
81 | if (!syscon) |
82 | return ERR_PTR(-ENODEV); | |
19a0f612 BA |
83 | |
84 | regmap = syscon_node_to_regmap(syscon); | |
4d41c8cb | 85 | of_node_put(syscon); |
19a0f612 | 86 | if (IS_ERR(regmap)) |
7a1e6fb1 | 87 | return regmap; |
19a0f612 | 88 | |
7a1e6fb1 | 89 | ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 1, base); |
19a0f612 BA |
90 | if (ret < 0) { |
91 | dev_err(&pdev->dev, "no offset in syscon\n"); | |
7a1e6fb1 | 92 | return ERR_PTR(-EINVAL); |
19a0f612 BA |
93 | } |
94 | ||
7a1e6fb1 | 95 | ret = of_property_read_u32_index(pdev->dev.of_node, "syscon", 2, stride); |
19a0f612 BA |
96 | if (ret < 0) { |
97 | dev_err(&pdev->dev, "no stride syscon\n"); | |
7a1e6fb1 | 98 | return ERR_PTR(-EINVAL); |
19a0f612 BA |
99 | } |
100 | ||
7a1e6fb1 BA |
101 | return regmap; |
102 | } | |
103 | ||
104 | static const struct regmap_config tcsr_mutex_config = { | |
105 | .reg_bits = 32, | |
106 | .reg_stride = 4, | |
107 | .val_bits = 32, | |
108 | .max_register = 0x40000, | |
109 | .fast_io = true, | |
110 | }; | |
111 | ||
112 | static struct regmap *qcom_hwspinlock_probe_mmio(struct platform_device *pdev, | |
113 | u32 *offset, u32 *stride) | |
114 | { | |
115 | struct device *dev = &pdev->dev; | |
116 | void __iomem *base; | |
117 | ||
118 | /* All modern platform has offset 0 and stride of 4k */ | |
119 | *offset = 0; | |
120 | *stride = 0x1000; | |
121 | ||
122 | base = devm_platform_ioremap_resource(pdev, 0); | |
123 | if (IS_ERR(base)) | |
124 | return ERR_CAST(base); | |
125 | ||
126 | return devm_regmap_init_mmio(dev, base, &tcsr_mutex_config); | |
127 | } | |
128 | ||
129 | static int qcom_hwspinlock_probe(struct platform_device *pdev) | |
130 | { | |
131 | struct hwspinlock_device *bank; | |
132 | struct reg_field field; | |
133 | struct regmap *regmap; | |
134 | size_t array_size; | |
135 | u32 stride; | |
136 | u32 base; | |
137 | int i; | |
138 | ||
139 | regmap = qcom_hwspinlock_probe_syscon(pdev, &base, &stride); | |
140 | if (IS_ERR(regmap) && PTR_ERR(regmap) == -ENODEV) | |
141 | regmap = qcom_hwspinlock_probe_mmio(pdev, &base, &stride); | |
142 | ||
143 | if (IS_ERR(regmap)) | |
144 | return PTR_ERR(regmap); | |
145 | ||
19a0f612 BA |
146 | array_size = QCOM_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock); |
147 | bank = devm_kzalloc(&pdev->dev, sizeof(*bank) + array_size, GFP_KERNEL); | |
148 | if (!bank) | |
149 | return -ENOMEM; | |
150 | ||
151 | platform_set_drvdata(pdev, bank); | |
152 | ||
153 | for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) { | |
154 | field.reg = base + i * stride; | |
155 | field.lsb = 0; | |
bd5717a4 | 156 | field.msb = 31; |
19a0f612 BA |
157 | |
158 | bank->lock[i].priv = devm_regmap_field_alloc(&pdev->dev, | |
159 | regmap, field); | |
160 | } | |
161 | ||
ed0611a6 BW |
162 | return devm_hwspin_lock_register(&pdev->dev, bank, &qcom_hwspinlock_ops, |
163 | 0, QCOM_MUTEX_NUM_LOCKS); | |
19a0f612 BA |
164 | } |
165 | ||
166 | static struct platform_driver qcom_hwspinlock_driver = { | |
167 | .probe = qcom_hwspinlock_probe, | |
19a0f612 BA |
168 | .driver = { |
169 | .name = "qcom_hwspinlock", | |
170 | .of_match_table = qcom_hwspinlock_of_match, | |
171 | }, | |
172 | }; | |
173 | ||
174 | static int __init qcom_hwspinlock_init(void) | |
175 | { | |
176 | return platform_driver_register(&qcom_hwspinlock_driver); | |
177 | } | |
178 | /* board init code might need to reserve hwspinlocks for predefined purposes */ | |
179 | postcore_initcall(qcom_hwspinlock_init); | |
180 | ||
181 | static void __exit qcom_hwspinlock_exit(void) | |
182 | { | |
183 | platform_driver_unregister(&qcom_hwspinlock_driver); | |
184 | } | |
185 | module_exit(qcom_hwspinlock_exit); | |
186 | ||
187 | MODULE_LICENSE("GPL v2"); | |
188 | MODULE_DESCRIPTION("Hardware spinlock driver for Qualcomm SoCs"); |