1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (c) 2013, The Linux Foundation. All rights reserved. |
4 | * Copyright (c) 2015, Sony Mobile Communications AB |
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> |
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 | struct qcom_hwspinlock_of_data { |
23 | u32 offset; |
24 | u32 stride; |
25 | const struct regmap_config *regmap_config; |
26 | }; |
27 | |
28 | static int qcom_hwspinlock_trylock(struct hwspinlock *lock) |
29 | { |
30 | struct regmap_field *field = lock->priv; |
31 | u32 lock_owner; |
32 | int ret; |
33 | |
34 | ret = regmap_field_write(field, QCOM_MUTEX_APPS_PROC_ID); |
35 | if (ret) |
36 | return ret; |
37 | |
38 | ret = regmap_field_read(field, val: &lock_owner); |
39 | if (ret) |
40 | return ret; |
41 | |
42 | return lock_owner == QCOM_MUTEX_APPS_PROC_ID; |
43 | } |
44 | |
45 | static void qcom_hwspinlock_unlock(struct hwspinlock *lock) |
46 | { |
47 | struct regmap_field *field = lock->priv; |
48 | u32 lock_owner; |
49 | int ret; |
50 | |
51 | ret = regmap_field_read(field, val: &lock_owner); |
52 | if (ret) { |
53 | pr_err("%s: unable to query spinlock owner\n" , __func__); |
54 | return; |
55 | } |
56 | |
57 | if (lock_owner != QCOM_MUTEX_APPS_PROC_ID) { |
58 | pr_err("%s: spinlock not owned by us (actual owner is %d)\n" , |
59 | __func__, lock_owner); |
60 | } |
61 | |
62 | ret = regmap_field_write(field, val: 0); |
63 | if (ret) |
64 | pr_err("%s: failed to unlock spinlock\n" , __func__); |
65 | } |
66 | |
67 | static const struct hwspinlock_ops qcom_hwspinlock_ops = { |
68 | .trylock = qcom_hwspinlock_trylock, |
69 | .unlock = qcom_hwspinlock_unlock, |
70 | }; |
71 | |
72 | static const struct regmap_config sfpb_mutex_config = { |
73 | .reg_bits = 32, |
74 | .reg_stride = 4, |
75 | .val_bits = 32, |
76 | .max_register = 0x100, |
77 | .fast_io = true, |
78 | }; |
79 | |
80 | static const struct qcom_hwspinlock_of_data of_sfpb_mutex = { |
81 | .offset = 0x4, |
82 | .stride = 0x4, |
83 | .regmap_config = &sfpb_mutex_config, |
84 | }; |
85 | |
86 | static const struct regmap_config tcsr_msm8226_mutex_config = { |
87 | .reg_bits = 32, |
88 | .reg_stride = 4, |
89 | .val_bits = 32, |
90 | .max_register = 0x1000, |
91 | .fast_io = true, |
92 | }; |
93 | |
94 | static const struct qcom_hwspinlock_of_data of_msm8226_tcsr_mutex = { |
95 | .offset = 0, |
96 | .stride = 0x80, |
97 | .regmap_config = &tcsr_msm8226_mutex_config, |
98 | }; |
99 | |
100 | static const struct regmap_config tcsr_mutex_config = { |
101 | .reg_bits = 32, |
102 | .reg_stride = 4, |
103 | .val_bits = 32, |
104 | .max_register = 0x20000, |
105 | .fast_io = true, |
106 | }; |
107 | |
108 | static const struct qcom_hwspinlock_of_data of_tcsr_mutex = { |
109 | .offset = 0, |
110 | .stride = 0x1000, |
111 | .regmap_config = &tcsr_mutex_config, |
112 | }; |
113 | |
114 | static const struct of_device_id qcom_hwspinlock_of_match[] = { |
115 | { .compatible = "qcom,sfpb-mutex" , .data = &of_sfpb_mutex }, |
116 | { .compatible = "qcom,tcsr-mutex" , .data = &of_tcsr_mutex }, |
117 | { .compatible = "qcom,apq8084-tcsr-mutex" , .data = &of_msm8226_tcsr_mutex }, |
118 | { .compatible = "qcom,msm8226-tcsr-mutex" , .data = &of_msm8226_tcsr_mutex }, |
119 | { .compatible = "qcom,msm8974-tcsr-mutex" , .data = &of_msm8226_tcsr_mutex }, |
120 | { .compatible = "qcom,msm8994-tcsr-mutex" , .data = &of_msm8226_tcsr_mutex }, |
121 | { } |
122 | }; |
123 | MODULE_DEVICE_TABLE(of, qcom_hwspinlock_of_match); |
124 | |
125 | static struct regmap *qcom_hwspinlock_probe_syscon(struct platform_device *pdev, |
126 | u32 *base, u32 *stride) |
127 | { |
128 | struct device_node *syscon; |
129 | struct regmap *regmap; |
130 | int ret; |
131 | |
132 | syscon = of_parse_phandle(np: pdev->dev.of_node, phandle_name: "syscon" , index: 0); |
133 | if (!syscon) |
134 | return ERR_PTR(error: -ENODEV); |
135 | |
136 | regmap = syscon_node_to_regmap(np: syscon); |
137 | of_node_put(node: syscon); |
138 | if (IS_ERR(ptr: regmap)) |
139 | return regmap; |
140 | |
141 | ret = of_property_read_u32_index(np: pdev->dev.of_node, propname: "syscon" , index: 1, out_value: base); |
142 | if (ret < 0) { |
143 | dev_err(&pdev->dev, "no offset in syscon\n" ); |
144 | return ERR_PTR(error: -EINVAL); |
145 | } |
146 | |
147 | ret = of_property_read_u32_index(np: pdev->dev.of_node, propname: "syscon" , index: 2, out_value: stride); |
148 | if (ret < 0) { |
149 | dev_err(&pdev->dev, "no stride syscon\n" ); |
150 | return ERR_PTR(error: -EINVAL); |
151 | } |
152 | |
153 | return regmap; |
154 | } |
155 | |
156 | static struct regmap *qcom_hwspinlock_probe_mmio(struct platform_device *pdev, |
157 | u32 *offset, u32 *stride) |
158 | { |
159 | const struct qcom_hwspinlock_of_data *data; |
160 | struct device *dev = &pdev->dev; |
161 | void __iomem *base; |
162 | |
163 | data = of_device_get_match_data(dev); |
164 | if (!data->regmap_config) |
165 | return ERR_PTR(error: -EINVAL); |
166 | |
167 | *offset = data->offset; |
168 | *stride = data->stride; |
169 | |
170 | base = devm_platform_ioremap_resource(pdev, index: 0); |
171 | if (IS_ERR(ptr: base)) |
172 | return ERR_CAST(ptr: base); |
173 | |
174 | return devm_regmap_init_mmio(dev, base, data->regmap_config); |
175 | } |
176 | |
177 | static int qcom_hwspinlock_probe(struct platform_device *pdev) |
178 | { |
179 | struct hwspinlock_device *bank; |
180 | struct reg_field field; |
181 | struct regmap *regmap; |
182 | size_t array_size; |
183 | u32 stride; |
184 | u32 base; |
185 | int i; |
186 | |
187 | regmap = qcom_hwspinlock_probe_syscon(pdev, base: &base, stride: &stride); |
188 | if (IS_ERR(ptr: regmap) && PTR_ERR(ptr: regmap) == -ENODEV) |
189 | regmap = qcom_hwspinlock_probe_mmio(pdev, offset: &base, stride: &stride); |
190 | |
191 | if (IS_ERR(ptr: regmap)) |
192 | return PTR_ERR(ptr: regmap); |
193 | |
194 | array_size = QCOM_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock); |
195 | bank = devm_kzalloc(dev: &pdev->dev, size: sizeof(*bank) + array_size, GFP_KERNEL); |
196 | if (!bank) |
197 | return -ENOMEM; |
198 | |
199 | platform_set_drvdata(pdev, data: bank); |
200 | |
201 | for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) { |
202 | field.reg = base + i * stride; |
203 | field.lsb = 0; |
204 | field.msb = 31; |
205 | |
206 | bank->lock[i].priv = devm_regmap_field_alloc(dev: &pdev->dev, |
207 | regmap, reg_field: field); |
208 | if (IS_ERR(ptr: bank->lock[i].priv)) |
209 | return PTR_ERR(ptr: bank->lock[i].priv); |
210 | } |
211 | |
212 | return devm_hwspin_lock_register(dev: &pdev->dev, bank, ops: &qcom_hwspinlock_ops, |
213 | base_id: 0, QCOM_MUTEX_NUM_LOCKS); |
214 | } |
215 | |
216 | static struct platform_driver qcom_hwspinlock_driver = { |
217 | .probe = qcom_hwspinlock_probe, |
218 | .driver = { |
219 | .name = "qcom_hwspinlock" , |
220 | .of_match_table = qcom_hwspinlock_of_match, |
221 | }, |
222 | }; |
223 | |
224 | static int __init qcom_hwspinlock_init(void) |
225 | { |
226 | return platform_driver_register(&qcom_hwspinlock_driver); |
227 | } |
228 | /* board init code might need to reserve hwspinlocks for predefined purposes */ |
229 | postcore_initcall(qcom_hwspinlock_init); |
230 | |
231 | static void __exit qcom_hwspinlock_exit(void) |
232 | { |
233 | platform_driver_unregister(&qcom_hwspinlock_driver); |
234 | } |
235 | module_exit(qcom_hwspinlock_exit); |
236 | |
237 | MODULE_LICENSE("GPL v2" ); |
238 | MODULE_DESCRIPTION("Hardware spinlock driver for Qualcomm SoCs" ); |
239 | |