xref: /freebsd-14.2/sys/dev/extres/phy/phy.c (revision 052d3c12)
1 /*-
2  * Copyright 2016 Michal Meloun <[email protected]>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27  #include <sys/cdefs.h>
28 __FBSDID("$FreeBSD$");
29 
30 #include "opt_platform.h"
31 #include <sys/param.h>
32 #include <sys/kernel.h>
33 #include <sys/kobj.h>
34 #include <sys/lock.h>
35 #include <sys/malloc.h>
36 #include <sys/queue.h>
37 #include <sys/systm.h>
38 #include <sys/sx.h>
39 
40 #ifdef FDT
41 #include <dev/ofw/ofw_bus.h>
42 #include <dev/ofw/ofw_bus_subr.h>
43 #endif
44 
45 #include  <dev/extres/phy/phy.h>
46 
47 #include "phydev_if.h"
48 
49 MALLOC_DEFINE(M_PHY, "phy", "Phy framework");
50 
51 /* Forward declarations. */
52 struct phy;
53 struct phynode;
54 
55 typedef TAILQ_HEAD(phynode_list, phynode) phynode_list_t;
56 typedef TAILQ_HEAD(phy_list, phy) phy_list_t;
57 
58 /* Default phy methods. */
59 static int phynode_method_init(struct phynode *phynode);
60 static int phynode_method_enable(struct phynode *phynode, bool disable);
61 static int phynode_method_status(struct phynode *phynode, int *status);
62 
63 
64 /*
65  * Phy controller methods.
66  */
67 static phynode_method_t phynode_methods[] = {
68 	PHYNODEMETHOD(phynode_init,		phynode_method_init),
69 	PHYNODEMETHOD(phynode_enable,		phynode_method_enable),
70 	PHYNODEMETHOD(phynode_status,		phynode_method_status),
71 
72 	PHYNODEMETHOD_END
73 };
74 DEFINE_CLASS_0(phynode, phynode_class, phynode_methods, 0);
75 
76 /*
77  * Phy node
78  */
79 struct phynode {
80 	KOBJ_FIELDS;
81 
82 	TAILQ_ENTRY(phynode)	phylist_link;	/* Global list entry */
83 	phy_list_t		consumers_list;	/* Consumers list */
84 
85 
86 	/* Details of this device. */
87 	const char		*name;		/* Globally unique name */
88 
89 	device_t		pdev;		/* Producer device_t */
90 	void			*softc;		/* Producer softc */
91 	intptr_t		id;		/* Per producer unique id */
92 #ifdef FDT
93 	 phandle_t		ofw_node;	/* OFW node of phy */
94 #endif
95 	struct sx		lock;		/* Lock for this phy */
96 	int			ref_cnt;	/* Reference counter */
97 	int			enable_cnt;	/* Enabled counter */
98 };
99 
100 struct phy {
101 	device_t		cdev;		/* consumer device*/
102 	struct phynode		*phynode;
103 	TAILQ_ENTRY(phy)	link;		/* Consumers list entry */
104 
105 	int			enable_cnt;
106 };
107 
108 static phynode_list_t phynode_list = TAILQ_HEAD_INITIALIZER(phynode_list);
109 
110 static struct sx		phynode_topo_lock;
111 SX_SYSINIT(phy_topology, &phynode_topo_lock, "Phy topology lock");
112 
113 #define PHY_TOPO_SLOCK()	sx_slock(&phynode_topo_lock)
114 #define PHY_TOPO_XLOCK()	sx_xlock(&phynode_topo_lock)
115 #define PHY_TOPO_UNLOCK()	sx_unlock(&phynode_topo_lock)
116 #define PHY_TOPO_ASSERT()	sx_assert(&phynode_topo_lock, SA_LOCKED)
117 #define PHY_TOPO_XASSERT() 	sx_assert(&phynode_topo_lock, SA_XLOCKED)
118 
119 #define PHYNODE_SLOCK(_sc)	sx_slock(&((_sc)->lock))
120 #define PHYNODE_XLOCK(_sc)	sx_xlock(&((_sc)->lock))
121 #define PHYNODE_UNLOCK(_sc)	sx_unlock(&((_sc)->lock))
122 
123 /* ----------------------------------------------------------------------------
124  *
125  * Default phy methods for base class.
126  *
127  */
128 
129 static int
130 phynode_method_init(struct phynode *phynode)
131 {
132 
133 	return (0);
134 }
135 
136 static int
137 phynode_method_enable(struct phynode *phynode, bool enable)
138 {
139 
140 	if (!enable)
141 		return (ENXIO);
142 
143 	return (0);
144 }
145 
146 static int
147 phynode_method_status(struct phynode *phynode, int *status)
148 {
149 	*status = PHY_STATUS_ENABLED;
150 	return (0);
151 }
152 
153 /* ----------------------------------------------------------------------------
154  *
155  * Internal functions.
156  *
157  */
158 /*
159  * Create and initialize phy object, but do not register it.
160  */
161 struct phynode *
162 phynode_create(device_t pdev, phynode_class_t phynode_class,
163     struct phynode_init_def *def)
164 {
165 	struct phynode *phynode;
166 
167 
168 	/* Create object and initialize it. */
169 	phynode = malloc(sizeof(struct phynode), M_PHY, M_WAITOK | M_ZERO);
170 	kobj_init((kobj_t)phynode, (kobj_class_t)phynode_class);
171 	sx_init(&phynode->lock, "Phy node lock");
172 
173 	/* Allocate softc if required. */
174 	if (phynode_class->size > 0) {
175 		phynode->softc = malloc(phynode_class->size, M_PHY,
176 		    M_WAITOK | M_ZERO);
177 	}
178 
179 	/* Rest of init. */
180 	TAILQ_INIT(&phynode->consumers_list);
181 	phynode->id = def->id;
182 	phynode->pdev = pdev;
183 #ifdef FDT
184 	phynode->ofw_node = def->ofw_node;
185 #endif
186 
187 	return (phynode);
188 }
189 
190 /* Register phy object. */
191 struct phynode *
192 phynode_register(struct phynode *phynode)
193 {
194 	int rv;
195 
196 #ifdef FDT
197 	if (phynode->ofw_node <= 0)
198 		phynode->ofw_node = ofw_bus_get_node(phynode->pdev);
199 	if (phynode->ofw_node <= 0)
200 		return (NULL);
201 #endif
202 
203 	rv = PHYNODE_INIT(phynode);
204 	if (rv != 0) {
205 		printf("PHYNODE_INIT failed: %d\n", rv);
206 		return (NULL);
207 	}
208 
209 	PHY_TOPO_XLOCK();
210 	TAILQ_INSERT_TAIL(&phynode_list, phynode, phylist_link);
211 	PHY_TOPO_UNLOCK();
212 #ifdef FDT
213 	OF_device_register_xref(OF_xref_from_node(phynode->ofw_node),
214 	    phynode->pdev);
215 #endif
216 	return (phynode);
217 }
218 
219 static struct phynode *
220 phynode_find_by_id(device_t dev, intptr_t id)
221 {
222 	struct phynode *entry;
223 
224 	PHY_TOPO_ASSERT();
225 
226 	TAILQ_FOREACH(entry, &phynode_list, phylist_link) {
227 		if ((entry->pdev == dev) && (entry->id ==  id))
228 			return (entry);
229 	}
230 
231 	return (NULL);
232 }
233 
234 /* --------------------------------------------------------------------------
235  *
236  * Phy providers interface
237  *
238  */
239 
240 void *
241 phynode_get_softc(struct phynode *phynode)
242 {
243 
244 	return (phynode->softc);
245 }
246 
247 device_t
248 phynode_get_device(struct phynode *phynode)
249 {
250 
251 	return (phynode->pdev);
252 }
253 
254 intptr_t phynode_get_id(struct phynode *phynode)
255 {
256 
257 	return (phynode->id);
258 }
259 
260 #ifdef FDT
261 phandle_t
262 phynode_get_ofw_node(struct phynode *phynode)
263 {
264 
265 	return (phynode->ofw_node);
266 }
267 #endif
268 
269 /* --------------------------------------------------------------------------
270  *
271  * Real consumers executive
272  *
273  */
274 
275 /*
276  * Enable phy.
277  */
278 int
279 phynode_enable(struct phynode *phynode)
280 {
281 	int rv;
282 
283 	PHY_TOPO_ASSERT();
284 
285 	PHYNODE_XLOCK(phynode);
286 	if (phynode->enable_cnt == 0) {
287 		rv = PHYNODE_ENABLE(phynode, true);
288 		if (rv != 0) {
289 			PHYNODE_UNLOCK(phynode);
290 			return (rv);
291 		}
292 	}
293 	phynode->enable_cnt++;
294 	PHYNODE_UNLOCK(phynode);
295 	return (0);
296 }
297 
298 /*
299  * Disable phy.
300  */
301 int
302 phynode_disable(struct phynode *phynode)
303 {
304 	int rv;
305 
306 	PHY_TOPO_ASSERT();
307 
308 	PHYNODE_XLOCK(phynode);
309 	if (phynode->enable_cnt == 1) {
310 		rv = PHYNODE_ENABLE(phynode, false);
311 		if (rv != 0) {
312 			PHYNODE_UNLOCK(phynode);
313 			return (rv);
314 		}
315 	}
316 	phynode->enable_cnt--;
317 	PHYNODE_UNLOCK(phynode);
318 	return (0);
319 }
320 
321 
322 /*
323  * Get phy status. (PHY_STATUS_*)
324  */
325 int
326 phynode_status(struct phynode *phynode, int *status)
327 {
328 	int rv;
329 
330 	PHY_TOPO_ASSERT();
331 
332 	PHYNODE_XLOCK(phynode);
333 	rv = PHYNODE_STATUS(phynode, status);
334 	PHYNODE_UNLOCK(phynode);
335 	return (rv);
336 }
337 
338  /* --------------------------------------------------------------------------
339  *
340  * Phy consumers interface.
341  *
342  */
343 
344 /* Helper function for phy_get*() */
345 static phy_t
346 phy_create(struct phynode *phynode, device_t cdev)
347 {
348 	struct phy *phy;
349 
350 	PHY_TOPO_ASSERT();
351 
352 	phy =  malloc(sizeof(struct phy), M_PHY, M_WAITOK | M_ZERO);
353 	phy->cdev = cdev;
354 	phy->phynode = phynode;
355 	phy->enable_cnt = 0;
356 
357 	PHYNODE_XLOCK(phynode);
358 	phynode->ref_cnt++;
359 	TAILQ_INSERT_TAIL(&phynode->consumers_list, phy, link);
360 	PHYNODE_UNLOCK(phynode);
361 
362 	return (phy);
363 }
364 
365 int
366 phy_enable(phy_t phy)
367 {
368 	int rv;
369 	struct phynode *phynode;
370 
371 	phynode = phy->phynode;
372 	KASSERT(phynode->ref_cnt > 0,
373 	    ("Attempt to access unreferenced phy.\n"));
374 
375 	PHY_TOPO_SLOCK();
376 	rv = phynode_enable(phynode);
377 	if (rv == 0)
378 		phy->enable_cnt++;
379 	PHY_TOPO_UNLOCK();
380 	return (rv);
381 }
382 
383 int
384 phy_disable(phy_t phy)
385 {
386 	int rv;
387 	struct phynode *phynode;
388 
389 	phynode = phy->phynode;
390 	KASSERT(phynode->ref_cnt > 0,
391 	   ("Attempt to access unreferenced phy.\n"));
392 	KASSERT(phy->enable_cnt > 0,
393 	   ("Attempt to disable already disabled phy.\n"));
394 
395 	PHY_TOPO_SLOCK();
396 	rv = phynode_disable(phynode);
397 	if (rv == 0)
398 		phy->enable_cnt--;
399 	PHY_TOPO_UNLOCK();
400 	return (rv);
401 }
402 
403 int
404 phy_status(phy_t phy, int *status)
405 {
406 	int rv;
407 	struct phynode *phynode;
408 
409 	phynode = phy->phynode;
410 	KASSERT(phynode->ref_cnt > 0,
411 	   ("Attempt to access unreferenced phy.\n"));
412 
413 	PHY_TOPO_SLOCK();
414 	rv = phynode_status(phynode, status);
415 	PHY_TOPO_UNLOCK();
416 	return (rv);
417 }
418 
419 int
420 phy_get_by_id(device_t consumer_dev, device_t provider_dev, intptr_t id,
421     phy_t *phy)
422 {
423 	struct phynode *phynode;
424 
425 	PHY_TOPO_SLOCK();
426 
427 	phynode = phynode_find_by_id(provider_dev, id);
428 	if (phynode == NULL) {
429 		PHY_TOPO_UNLOCK();
430 		return (ENODEV);
431 	}
432 	*phy = phy_create(phynode, consumer_dev);
433 	PHY_TOPO_UNLOCK();
434 
435 	return (0);
436 }
437 
438 void
439 phy_release(phy_t phy)
440 {
441 	struct phynode *phynode;
442 
443 	phynode = phy->phynode;
444 	KASSERT(phynode->ref_cnt > 0,
445 	   ("Attempt to access unreferenced phy.\n"));
446 
447 	PHY_TOPO_SLOCK();
448 	while (phy->enable_cnt > 0) {
449 		phynode_disable(phynode);
450 		phy->enable_cnt--;
451 	}
452 	PHYNODE_XLOCK(phynode);
453 	TAILQ_REMOVE(&phynode->consumers_list, phy, link);
454 	phynode->ref_cnt--;
455 	PHYNODE_UNLOCK(phynode);
456 	PHY_TOPO_UNLOCK();
457 
458 	free(phy, M_PHY);
459 }
460 
461 #ifdef FDT
462 int phydev_default_ofw_map(device_t provider, phandle_t xref, int ncells,
463     pcell_t *cells, intptr_t *id)
464 {
465 	struct phynode *entry;
466 	phandle_t node;
467 
468 	/* Single device can register multiple subnodes. */
469 	if (ncells == 0) {
470 
471 		node = OF_node_from_xref(xref);
472 		PHY_TOPO_XLOCK();
473 		TAILQ_FOREACH(entry, &phynode_list, phylist_link) {
474 			if ((entry->pdev == provider) &&
475 			    (entry->ofw_node == node)) {
476 				*id = entry->id;
477 				PHY_TOPO_UNLOCK();
478 				return (0);
479 			}
480 		}
481 		PHY_TOPO_UNLOCK();
482 		return (ERANGE);
483 	}
484 
485 	/* First cell is ID. */
486 	if (ncells == 1) {
487 		*id = cells[0];
488 		return (0);
489 	}
490 
491 	/* No default way how to get ID, custom mapper is required. */
492 	return  (ERANGE);
493 }
494 
495 int
496 phy_get_by_ofw_idx(device_t consumer_dev, phandle_t cnode, int idx, phy_t *phy)
497 {
498 	phandle_t xnode;
499 	pcell_t *cells;
500 	device_t phydev;
501 	int ncells, rv;
502 	intptr_t id;
503 
504 	if (cnode <= 0)
505 		cnode = ofw_bus_get_node(consumer_dev);
506 	if (cnode <= 0) {
507 		device_printf(consumer_dev,
508 		    "%s called on not ofw based device\n", __func__);
509 		return (ENXIO);
510 	}
511 	rv = ofw_bus_parse_xref_list_alloc(cnode, "phys", "#phy-cells", idx,
512 	    &xnode, &ncells, &cells);
513 	if (rv != 0)
514 		return (rv);
515 
516 	/* Tranlate provider to device. */
517 	phydev = OF_device_from_xref(xnode);
518 	if (phydev == NULL) {
519 		OF_prop_free(cells);
520 		return (ENODEV);
521 	}
522 	/* Map phy to number. */
523 	rv = PHYDEV_MAP(phydev, xnode, ncells, cells, &id);
524 	OF_prop_free(cells);
525 	if (rv != 0)
526 		return (rv);
527 
528 	return (phy_get_by_id(consumer_dev, phydev, id, phy));
529 }
530 
531 int
532 phy_get_by_ofw_name(device_t consumer_dev, phandle_t cnode, char *name,
533     phy_t *phy)
534 {
535 	int rv, idx;
536 
537 	if (cnode <= 0)
538 		cnode = ofw_bus_get_node(consumer_dev);
539 	if (cnode <= 0) {
540 		device_printf(consumer_dev,
541 		    "%s called on not ofw based device\n",  __func__);
542 		return (ENXIO);
543 	}
544 	rv = ofw_bus_find_string_index(cnode, "phy-names", name, &idx);
545 	if (rv != 0)
546 		return (rv);
547 	return (phy_get_by_ofw_idx(consumer_dev, cnode, idx, phy));
548 }
549 
550 int
551 phy_get_by_ofw_property(device_t consumer_dev, phandle_t cnode, char *name,
552     phy_t *phy)
553 {
554 	pcell_t *cells;
555 	device_t phydev;
556 	int ncells, rv;
557 	intptr_t id;
558 
559 	if (cnode <= 0)
560 		cnode = ofw_bus_get_node(consumer_dev);
561 	if (cnode <= 0) {
562 		device_printf(consumer_dev,
563 		    "%s called on not ofw based device\n", __func__);
564 		return (ENXIO);
565 	}
566 	ncells = OF_getencprop_alloc(cnode, name, sizeof(pcell_t),
567 	    (void **)&cells);
568 	if (ncells < 1)
569 		return (ENXIO);
570 
571 	/* Tranlate provider to device. */
572 	phydev = OF_device_from_xref(cells[0]);
573 	if (phydev == NULL) {
574 		OF_prop_free(cells);
575 		return (ENODEV);
576 	}
577 	/* Map phy to number. */
578 	rv = PHYDEV_MAP(phydev, cells[0], ncells - 1 , cells + 1, &id);
579 	OF_prop_free(cells);
580 	if (rv != 0)
581 		return (rv);
582 
583 	return (phy_get_by_id(consumer_dev, phydev, id, phy));
584 }
585 #endif
586