Commit | Line | Data |
---|---|---|
58ef4ece BA |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. | |
4 | * Copyright (c) 2022, Linaro Ltd | |
5 | */ | |
6 | #include <linux/auxiliary_bus.h> | |
7 | #include <linux/module.h> | |
6484be9d | 8 | #include <linux/of.h> |
58ef4ece BA |
9 | #include <linux/platform_device.h> |
10 | #include <linux/rpmsg.h> | |
11 | #include <linux/slab.h> | |
12 | #include <linux/soc/qcom/pdr.h> | |
13 | #include <linux/soc/qcom/pmic_glink.h> | |
93299336 | 14 | #include <linux/spinlock.h> |
58ef4ece | 15 | |
ff642773 NA |
16 | enum { |
17 | PMIC_GLINK_CLIENT_BATT = 0, | |
18 | PMIC_GLINK_CLIENT_ALTMODE, | |
19 | PMIC_GLINK_CLIENT_UCSI, | |
20 | }; | |
21 | ||
58ef4ece BA |
22 | struct pmic_glink { |
23 | struct device *dev; | |
24 | struct pdr_handle *pdr; | |
25 | ||
26 | struct rpmsg_endpoint *ept; | |
27 | ||
ff642773 NA |
28 | unsigned long client_mask; |
29 | ||
58ef4ece BA |
30 | struct auxiliary_device altmode_aux; |
31 | struct auxiliary_device ps_aux; | |
32 | struct auxiliary_device ucsi_aux; | |
33 | ||
34 | /* serializing client_state and pdr_state updates */ | |
35 | struct mutex state_lock; | |
36 | unsigned int client_state; | |
37 | unsigned int pdr_state; | |
38 | ||
39 | /* serializing clients list updates */ | |
93299336 | 40 | spinlock_t client_lock; |
58ef4ece BA |
41 | struct list_head clients; |
42 | }; | |
43 | ||
44 | static struct pmic_glink *__pmic_glink; | |
45 | static DEFINE_MUTEX(__pmic_glink_lock); | |
46 | ||
47 | struct pmic_glink_client { | |
48 | struct list_head node; | |
49 | ||
50 | struct pmic_glink *pg; | |
51 | unsigned int id; | |
52 | ||
53 | void (*cb)(const void *data, size_t len, void *priv); | |
54 | void (*pdr_notify)(void *priv, int state); | |
55 | void *priv; | |
56 | }; | |
57 | ||
58 | static void _devm_pmic_glink_release_client(struct device *dev, void *res) | |
59 | { | |
60 | struct pmic_glink_client *client = (struct pmic_glink_client *)res; | |
61 | struct pmic_glink *pg = client->pg; | |
93299336 | 62 | unsigned long flags; |
58ef4ece | 63 | |
93299336 | 64 | spin_lock_irqsave(&pg->client_lock, flags); |
58ef4ece | 65 | list_del(&client->node); |
93299336 | 66 | spin_unlock_irqrestore(&pg->client_lock, flags); |
58ef4ece BA |
67 | } |
68 | ||
69 | struct pmic_glink_client *devm_pmic_glink_register_client(struct device *dev, | |
70 | unsigned int id, | |
71 | void (*cb)(const void *, size_t, void *), | |
72 | void (*pdr)(void *, int), | |
73 | void *priv) | |
74 | { | |
75 | struct pmic_glink_client *client; | |
76 | struct pmic_glink *pg = dev_get_drvdata(dev->parent); | |
93299336 | 77 | unsigned long flags; |
58ef4ece BA |
78 | |
79 | client = devres_alloc(_devm_pmic_glink_release_client, sizeof(*client), GFP_KERNEL); | |
80 | if (!client) | |
81 | return ERR_PTR(-ENOMEM); | |
82 | ||
83 | client->pg = pg; | |
84 | client->id = id; | |
85 | client->cb = cb; | |
86 | client->pdr_notify = pdr; | |
87 | client->priv = priv; | |
88 | ||
d6cbce2c | 89 | mutex_lock(&pg->state_lock); |
93299336 | 90 | spin_lock_irqsave(&pg->client_lock, flags); |
d6cbce2c | 91 | |
58ef4ece | 92 | list_add(&client->node, &pg->clients); |
d6cbce2c DB |
93 | client->pdr_notify(client->priv, pg->client_state); |
94 | ||
93299336 | 95 | spin_unlock_irqrestore(&pg->client_lock, flags); |
d6cbce2c | 96 | mutex_unlock(&pg->state_lock); |
58ef4ece BA |
97 | |
98 | devres_add(dev, client); | |
99 | ||
100 | return client; | |
101 | } | |
102 | EXPORT_SYMBOL_GPL(devm_pmic_glink_register_client); | |
103 | ||
104 | int pmic_glink_send(struct pmic_glink_client *client, void *data, size_t len) | |
105 | { | |
106 | struct pmic_glink *pg = client->pg; | |
107 | ||
108 | return rpmsg_send(pg->ept, data, len); | |
109 | } | |
110 | EXPORT_SYMBOL_GPL(pmic_glink_send); | |
111 | ||
112 | static int pmic_glink_rpmsg_callback(struct rpmsg_device *rpdev, void *data, | |
113 | int len, void *priv, u32 addr) | |
114 | { | |
115 | struct pmic_glink_client *client; | |
116 | struct pmic_glink_hdr *hdr; | |
117 | struct pmic_glink *pg = dev_get_drvdata(&rpdev->dev); | |
93299336 | 118 | unsigned long flags; |
58ef4ece BA |
119 | |
120 | if (len < sizeof(*hdr)) { | |
121 | dev_warn(pg->dev, "ignoring truncated message\n"); | |
122 | return 0; | |
123 | } | |
124 | ||
125 | hdr = data; | |
126 | ||
93299336 | 127 | spin_lock_irqsave(&pg->client_lock, flags); |
58ef4ece BA |
128 | list_for_each_entry(client, &pg->clients, node) { |
129 | if (client->id == le32_to_cpu(hdr->owner)) | |
130 | client->cb(data, len, client->priv); | |
131 | } | |
93299336 | 132 | spin_unlock_irqrestore(&pg->client_lock, flags); |
58ef4ece BA |
133 | |
134 | return 0; | |
135 | } | |
136 | ||
137 | static void pmic_glink_aux_release(struct device *dev) {} | |
138 | ||
139 | static int pmic_glink_add_aux_device(struct pmic_glink *pg, | |
140 | struct auxiliary_device *aux, | |
141 | const char *name) | |
142 | { | |
143 | struct device *parent = pg->dev; | |
144 | int ret; | |
145 | ||
146 | aux->name = name; | |
147 | aux->dev.parent = parent; | |
148 | aux->dev.release = pmic_glink_aux_release; | |
149 | device_set_of_node_from_dev(&aux->dev, parent); | |
150 | ret = auxiliary_device_init(aux); | |
151 | if (ret) | |
152 | return ret; | |
153 | ||
154 | ret = auxiliary_device_add(aux); | |
155 | if (ret) | |
156 | auxiliary_device_uninit(aux); | |
157 | ||
158 | return ret; | |
159 | } | |
160 | ||
161 | static void pmic_glink_del_aux_device(struct pmic_glink *pg, | |
162 | struct auxiliary_device *aux) | |
163 | { | |
164 | auxiliary_device_delete(aux); | |
165 | auxiliary_device_uninit(aux); | |
166 | } | |
167 | ||
168 | static void pmic_glink_state_notify_clients(struct pmic_glink *pg) | |
169 | { | |
170 | struct pmic_glink_client *client; | |
171 | unsigned int new_state = pg->client_state; | |
93299336 | 172 | unsigned long flags; |
58ef4ece BA |
173 | |
174 | if (pg->client_state != SERVREG_SERVICE_STATE_UP) { | |
175 | if (pg->pdr_state == SERVREG_SERVICE_STATE_UP && pg->ept) | |
176 | new_state = SERVREG_SERVICE_STATE_UP; | |
177 | } else { | |
178 | if (pg->pdr_state == SERVREG_SERVICE_STATE_UP && pg->ept) | |
179 | new_state = SERVREG_SERVICE_STATE_DOWN; | |
180 | } | |
181 | ||
182 | if (new_state != pg->client_state) { | |
93299336 | 183 | spin_lock_irqsave(&pg->client_lock, flags); |
58ef4ece BA |
184 | list_for_each_entry(client, &pg->clients, node) |
185 | client->pdr_notify(client->priv, new_state); | |
93299336 | 186 | spin_unlock_irqrestore(&pg->client_lock, flags); |
58ef4ece BA |
187 | pg->client_state = new_state; |
188 | } | |
189 | } | |
190 | ||
191 | static void pmic_glink_pdr_callback(int state, char *svc_path, void *priv) | |
192 | { | |
193 | struct pmic_glink *pg = priv; | |
194 | ||
195 | mutex_lock(&pg->state_lock); | |
196 | pg->pdr_state = state; | |
197 | ||
198 | pmic_glink_state_notify_clients(pg); | |
199 | mutex_unlock(&pg->state_lock); | |
200 | } | |
201 | ||
202 | static int pmic_glink_rpmsg_probe(struct rpmsg_device *rpdev) | |
203 | { | |
204 | struct pmic_glink *pg = __pmic_glink; | |
205 | int ret = 0; | |
206 | ||
207 | mutex_lock(&__pmic_glink_lock); | |
208 | if (!pg) { | |
209 | ret = dev_err_probe(&rpdev->dev, -ENODEV, "no pmic_glink device to attach to\n"); | |
210 | goto out_unlock; | |
211 | } | |
212 | ||
213 | dev_set_drvdata(&rpdev->dev, pg); | |
214 | ||
215 | mutex_lock(&pg->state_lock); | |
216 | pg->ept = rpdev->ept; | |
217 | pmic_glink_state_notify_clients(pg); | |
218 | mutex_unlock(&pg->state_lock); | |
219 | ||
220 | out_unlock: | |
221 | mutex_unlock(&__pmic_glink_lock); | |
222 | return ret; | |
223 | } | |
224 | ||
225 | static void pmic_glink_rpmsg_remove(struct rpmsg_device *rpdev) | |
226 | { | |
227 | struct pmic_glink *pg; | |
228 | ||
229 | mutex_lock(&__pmic_glink_lock); | |
230 | pg = __pmic_glink; | |
231 | if (!pg) | |
232 | goto out_unlock; | |
233 | ||
234 | mutex_lock(&pg->state_lock); | |
235 | pg->ept = NULL; | |
236 | pmic_glink_state_notify_clients(pg); | |
237 | mutex_unlock(&pg->state_lock); | |
238 | out_unlock: | |
239 | mutex_unlock(&__pmic_glink_lock); | |
240 | } | |
241 | ||
242 | static const struct rpmsg_device_id pmic_glink_rpmsg_id_match[] = { | |
243 | { "PMIC_RTR_ADSP_APPS" }, | |
244 | {} | |
245 | }; | |
246 | ||
247 | static struct rpmsg_driver pmic_glink_rpmsg_driver = { | |
248 | .probe = pmic_glink_rpmsg_probe, | |
249 | .remove = pmic_glink_rpmsg_remove, | |
250 | .callback = pmic_glink_rpmsg_callback, | |
251 | .id_table = pmic_glink_rpmsg_id_match, | |
252 | .drv = { | |
253 | .name = "qcom_pmic_glink_rpmsg", | |
254 | }, | |
255 | }; | |
256 | ||
257 | static int pmic_glink_probe(struct platform_device *pdev) | |
258 | { | |
ff642773 | 259 | const unsigned long *match_data; |
58ef4ece BA |
260 | struct pdr_service *service; |
261 | struct pmic_glink *pg; | |
262 | int ret; | |
263 | ||
264 | pg = devm_kzalloc(&pdev->dev, sizeof(*pg), GFP_KERNEL); | |
265 | if (!pg) | |
266 | return -ENOMEM; | |
267 | ||
268 | dev_set_drvdata(&pdev->dev, pg); | |
269 | ||
270 | pg->dev = &pdev->dev; | |
271 | ||
272 | INIT_LIST_HEAD(&pg->clients); | |
93299336 | 273 | spin_lock_init(&pg->client_lock); |
58ef4ece BA |
274 | mutex_init(&pg->state_lock); |
275 | ||
ff642773 | 276 | match_data = (unsigned long *)of_device_get_match_data(&pdev->dev); |
4db09e7b DB |
277 | if (!match_data) |
278 | return -EINVAL; | |
279 | ||
280 | pg->client_mask = *match_data; | |
ff642773 | 281 | |
f79ee787 RC |
282 | pg->pdr = pdr_handle_alloc(pmic_glink_pdr_callback, pg); |
283 | if (IS_ERR(pg->pdr)) { | |
284 | ret = dev_err_probe(&pdev->dev, PTR_ERR(pg->pdr), | |
285 | "failed to initialize pdr\n"); | |
286 | return ret; | |
287 | } | |
288 | ||
ff642773 NA |
289 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_UCSI)) { |
290 | ret = pmic_glink_add_aux_device(pg, &pg->ucsi_aux, "ucsi"); | |
291 | if (ret) | |
f79ee787 | 292 | goto out_release_pdr_handle; |
ff642773 NA |
293 | } |
294 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_ALTMODE)) { | |
295 | ret = pmic_glink_add_aux_device(pg, &pg->altmode_aux, "altmode"); | |
296 | if (ret) | |
297 | goto out_release_ucsi_aux; | |
298 | } | |
299 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_BATT)) { | |
300 | ret = pmic_glink_add_aux_device(pg, &pg->ps_aux, "power-supply"); | |
301 | if (ret) | |
302 | goto out_release_altmode_aux; | |
303 | } | |
58ef4ece | 304 | |
58ef4ece BA |
305 | service = pdr_add_lookup(pg->pdr, "tms/servreg", "msm/adsp/charger_pd"); |
306 | if (IS_ERR(service)) { | |
307 | ret = dev_err_probe(&pdev->dev, PTR_ERR(service), | |
308 | "failed adding pdr lookup for charger_pd\n"); | |
f79ee787 | 309 | goto out_release_aux_devices; |
58ef4ece BA |
310 | } |
311 | ||
312 | mutex_lock(&__pmic_glink_lock); | |
313 | __pmic_glink = pg; | |
314 | mutex_unlock(&__pmic_glink_lock); | |
315 | ||
316 | return 0; | |
317 | ||
58ef4ece | 318 | out_release_aux_devices: |
ff642773 NA |
319 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_BATT)) |
320 | pmic_glink_del_aux_device(pg, &pg->ps_aux); | |
58ef4ece | 321 | out_release_altmode_aux: |
ff642773 NA |
322 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_ALTMODE)) |
323 | pmic_glink_del_aux_device(pg, &pg->altmode_aux); | |
324 | out_release_ucsi_aux: | |
325 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_UCSI)) | |
326 | pmic_glink_del_aux_device(pg, &pg->ucsi_aux); | |
f79ee787 RC |
327 | out_release_pdr_handle: |
328 | pdr_handle_release(pg->pdr); | |
58ef4ece BA |
329 | |
330 | return ret; | |
331 | } | |
332 | ||
4b3373e4 | 333 | static void pmic_glink_remove(struct platform_device *pdev) |
58ef4ece BA |
334 | { |
335 | struct pmic_glink *pg = dev_get_drvdata(&pdev->dev); | |
336 | ||
337 | pdr_handle_release(pg->pdr); | |
338 | ||
ff642773 NA |
339 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_BATT)) |
340 | pmic_glink_del_aux_device(pg, &pg->ps_aux); | |
341 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_ALTMODE)) | |
342 | pmic_glink_del_aux_device(pg, &pg->altmode_aux); | |
343 | if (pg->client_mask & BIT(PMIC_GLINK_CLIENT_UCSI)) | |
344 | pmic_glink_del_aux_device(pg, &pg->ucsi_aux); | |
58ef4ece BA |
345 | |
346 | mutex_lock(&__pmic_glink_lock); | |
347 | __pmic_glink = NULL; | |
348 | mutex_unlock(&__pmic_glink_lock); | |
58ef4ece BA |
349 | } |
350 | ||
4db09e7b DB |
351 | static const unsigned long pmic_glink_sc8180x_client_mask = BIT(PMIC_GLINK_CLIENT_BATT) | |
352 | BIT(PMIC_GLINK_CLIENT_ALTMODE); | |
353 | ||
ff642773 | 354 | static const unsigned long pmic_glink_sm8450_client_mask = BIT(PMIC_GLINK_CLIENT_BATT) | |
4b11fa4f NA |
355 | BIT(PMIC_GLINK_CLIENT_ALTMODE) | |
356 | BIT(PMIC_GLINK_CLIENT_UCSI); | |
357 | ||
58ef4ece | 358 | static const struct of_device_id pmic_glink_of_match[] = { |
4db09e7b | 359 | { .compatible = "qcom,sc8180x-pmic-glink", .data = &pmic_glink_sc8180x_client_mask }, |
3581cb91 | 360 | { .compatible = "qcom,sc8280xp-pmic-glink", .data = &pmic_glink_sc8180x_client_mask }, |
4db09e7b | 361 | { .compatible = "qcom,pmic-glink", .data = &pmic_glink_sm8450_client_mask }, |
58ef4ece BA |
362 | {} |
363 | }; | |
364 | MODULE_DEVICE_TABLE(of, pmic_glink_of_match); | |
365 | ||
366 | static struct platform_driver pmic_glink_driver = { | |
367 | .probe = pmic_glink_probe, | |
4b3373e4 | 368 | .remove_new = pmic_glink_remove, |
58ef4ece BA |
369 | .driver = { |
370 | .name = "qcom_pmic_glink", | |
371 | .of_match_table = pmic_glink_of_match, | |
372 | }, | |
373 | }; | |
374 | ||
375 | static int pmic_glink_init(void) | |
376 | { | |
377 | platform_driver_register(&pmic_glink_driver); | |
378 | register_rpmsg_driver(&pmic_glink_rpmsg_driver); | |
379 | ||
380 | return 0; | |
27117558 | 381 | } |
58ef4ece BA |
382 | module_init(pmic_glink_init); |
383 | ||
384 | static void pmic_glink_exit(void) | |
385 | { | |
386 | unregister_rpmsg_driver(&pmic_glink_rpmsg_driver); | |
387 | platform_driver_unregister(&pmic_glink_driver); | |
27117558 | 388 | } |
58ef4ece BA |
389 | module_exit(pmic_glink_exit); |
390 | ||
391 | MODULE_DESCRIPTION("Qualcomm PMIC GLINK driver"); | |
392 | MODULE_LICENSE("GPL"); |