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