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