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,ipq6018-tcsr-mutex" , .data = &of_msm8226_tcsr_mutex }, |
119 | { .compatible = "qcom,msm8226-tcsr-mutex" , .data = &of_msm8226_tcsr_mutex }, |
120 | { .compatible = "qcom,msm8974-tcsr-mutex" , .data = &of_msm8226_tcsr_mutex }, |
121 | { .compatible = "qcom,msm8994-tcsr-mutex" , .data = &of_msm8226_tcsr_mutex }, |
122 | { } |
123 | }; |
124 | MODULE_DEVICE_TABLE(of, qcom_hwspinlock_of_match); |
125 | |
126 | static struct regmap *qcom_hwspinlock_probe_syscon(struct platform_device *pdev, |
127 | u32 *base, u32 *stride) |
128 | { |
129 | struct device_node *syscon; |
130 | struct regmap *regmap; |
131 | int ret; |
132 | |
133 | syscon = of_parse_phandle(np: pdev->dev.of_node, phandle_name: "syscon" , index: 0); |
134 | if (!syscon) |
135 | return ERR_PTR(error: -ENODEV); |
136 | |
137 | regmap = syscon_node_to_regmap(np: syscon); |
138 | of_node_put(node: syscon); |
139 | if (IS_ERR(ptr: regmap)) |
140 | return regmap; |
141 | |
142 | ret = of_property_read_u32_index(np: pdev->dev.of_node, propname: "syscon" , index: 1, out_value: base); |
143 | if (ret < 0) { |
144 | dev_err(&pdev->dev, "no offset in syscon\n" ); |
145 | return ERR_PTR(error: -EINVAL); |
146 | } |
147 | |
148 | ret = of_property_read_u32_index(np: pdev->dev.of_node, propname: "syscon" , index: 2, out_value: stride); |
149 | if (ret < 0) { |
150 | dev_err(&pdev->dev, "no stride syscon\n" ); |
151 | return ERR_PTR(error: -EINVAL); |
152 | } |
153 | |
154 | return regmap; |
155 | } |
156 | |
157 | static struct regmap *qcom_hwspinlock_probe_mmio(struct platform_device *pdev, |
158 | u32 *offset, u32 *stride) |
159 | { |
160 | const struct qcom_hwspinlock_of_data *data; |
161 | struct device *dev = &pdev->dev; |
162 | void __iomem *base; |
163 | |
164 | data = of_device_get_match_data(dev); |
165 | if (!data->regmap_config) |
166 | return ERR_PTR(error: -EINVAL); |
167 | |
168 | *offset = data->offset; |
169 | *stride = data->stride; |
170 | |
171 | base = devm_platform_ioremap_resource(pdev, index: 0); |
172 | if (IS_ERR(ptr: base)) |
173 | return ERR_CAST(ptr: base); |
174 | |
175 | return devm_regmap_init_mmio(dev, base, data->regmap_config); |
176 | } |
177 | |
178 | static int qcom_hwspinlock_probe(struct platform_device *pdev) |
179 | { |
180 | struct hwspinlock_device *bank; |
181 | struct reg_field field; |
182 | struct regmap *regmap; |
183 | size_t array_size; |
184 | u32 stride; |
185 | u32 base; |
186 | int i; |
187 | |
188 | regmap = qcom_hwspinlock_probe_syscon(pdev, base: &base, stride: &stride); |
189 | if (IS_ERR(ptr: regmap) && PTR_ERR(ptr: regmap) == -ENODEV) |
190 | regmap = qcom_hwspinlock_probe_mmio(pdev, offset: &base, stride: &stride); |
191 | |
192 | if (IS_ERR(ptr: regmap)) |
193 | return PTR_ERR(ptr: regmap); |
194 | |
195 | array_size = QCOM_MUTEX_NUM_LOCKS * sizeof(struct hwspinlock); |
196 | bank = devm_kzalloc(dev: &pdev->dev, size: sizeof(*bank) + array_size, GFP_KERNEL); |
197 | if (!bank) |
198 | return -ENOMEM; |
199 | |
200 | platform_set_drvdata(pdev, data: bank); |
201 | |
202 | for (i = 0; i < QCOM_MUTEX_NUM_LOCKS; i++) { |
203 | field.reg = base + i * stride; |
204 | field.lsb = 0; |
205 | field.msb = 31; |
206 | |
207 | bank->lock[i].priv = devm_regmap_field_alloc(dev: &pdev->dev, |
208 | regmap, reg_field: field); |
209 | if (IS_ERR(ptr: bank->lock[i].priv)) |
210 | return PTR_ERR(ptr: bank->lock[i].priv); |
211 | } |
212 | |
213 | return devm_hwspin_lock_register(dev: &pdev->dev, bank, ops: &qcom_hwspinlock_ops, |
214 | base_id: 0, QCOM_MUTEX_NUM_LOCKS); |
215 | } |
216 | |
217 | static struct platform_driver qcom_hwspinlock_driver = { |
218 | .probe = qcom_hwspinlock_probe, |
219 | .driver = { |
220 | .name = "qcom_hwspinlock" , |
221 | .of_match_table = qcom_hwspinlock_of_match, |
222 | }, |
223 | }; |
224 | |
225 | static int __init qcom_hwspinlock_init(void) |
226 | { |
227 | return platform_driver_register(&qcom_hwspinlock_driver); |
228 | } |
229 | /* board init code might need to reserve hwspinlocks for predefined purposes */ |
230 | postcore_initcall(qcom_hwspinlock_init); |
231 | |
232 | static void __exit qcom_hwspinlock_exit(void) |
233 | { |
234 | platform_driver_unregister(&qcom_hwspinlock_driver); |
235 | } |
236 | module_exit(qcom_hwspinlock_exit); |
237 | |
238 | MODULE_LICENSE("GPL v2" ); |
239 | MODULE_DESCRIPTION("Hardware spinlock driver for Qualcomm SoCs" ); |
240 | |