phy.c revision 332025
1/*-
2 * Copyright 2016 Michal Meloun <mmel@FreeBSD.org>
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: stable/11/sys/dev/extres/phy/phy.c 332025 2018-04-04 13:23:06Z mmel $");
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
49MALLOC_DEFINE(M_PHY, "phy", "Phy framework");
50
51/* Forward declarations. */
52struct phy;
53struct phynode;
54
55typedef TAILQ_HEAD(phynode_list, phynode) phynode_list_t;
56typedef TAILQ_HEAD(phy_list, phy) phy_list_t;
57
58/* Default phy methods. */
59static int phynode_method_init(struct phynode *phynode);
60static int phynode_method_enable(struct phynode *phynode, bool disable);
61static int phynode_method_status(struct phynode *phynode, int *status);
62
63
64/*
65 * Phy controller methods.
66 */
67static 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};
74DEFINE_CLASS_0(phynode, phynode_class, phynode_methods, 0);
75
76/*
77 * Phy node
78 */
79struct 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
100struct 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
108static phynode_list_t phynode_list = TAILQ_HEAD_INITIALIZER(phynode_list);
109
110static struct sx		phynode_topo_lock;
111SX_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
129static int
130phynode_method_init(struct phynode *phynode)
131{
132
133	return (0);
134}
135
136static int
137phynode_method_enable(struct phynode *phynode, bool enable)
138{
139
140	if (!enable)
141		return (ENXIO);
142
143	return (0);
144}
145
146static int
147phynode_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 */
161struct phynode *
162phynode_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. */
191struct phynode *
192phynode_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
219static struct phynode *
220phynode_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
240void *
241phynode_get_softc(struct phynode *phynode)
242{
243
244	return (phynode->softc);
245}
246
247device_t
248phynode_get_device(struct phynode *phynode)
249{
250
251	return (phynode->pdev);
252}
253
254intptr_t phynode_get_id(struct phynode *phynode)
255{
256
257	return (phynode->id);
258}
259
260#ifdef FDT
261phandle_t
262phynode_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 */
278int
279phynode_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 */
301int
302phynode_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 */
325int
326phynode_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*() */
345static phy_t
346phy_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
365int
366phy_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
383int
384phy_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
403int
404phy_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
419int
420phy_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
438void
439phy_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
462int 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
495int
496phy_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
531int
532phy_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
550int
551phy_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