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$");
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#include  <dev/extres/phy/phy_internal.h>
47
48#include "phydev_if.h"
49
50MALLOC_DEFINE(M_PHY, "phy", "Phy framework");
51
52/* Default phy methods. */
53static int phynode_method_init(struct phynode *phynode);
54static int phynode_method_enable(struct phynode *phynode, bool disable);
55static int phynode_method_status(struct phynode *phynode, int *status);
56
57
58/*
59 * Phy controller methods.
60 */
61static 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};
68DEFINE_CLASS_0(phynode, phynode_class, phynode_methods, 0);
69
70static phynode_list_t phynode_list = TAILQ_HEAD_INITIALIZER(phynode_list);
71struct sx phynode_topo_lock;
72SX_SYSINIT(phy_topology, &phynode_topo_lock, "Phy topology lock");
73
74/* ----------------------------------------------------------------------------
75 *
76 * Default phy methods for base class.
77 *
78 */
79
80static int
81phynode_method_init(struct phynode *phynode)
82{
83
84	return (0);
85}
86
87static int
88phynode_method_enable(struct phynode *phynode, bool enable)
89{
90
91	if (!enable)
92		return (ENXIO);
93
94	return (0);
95}
96
97static int
98phynode_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 */
112struct phynode *
113phynode_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. */
142struct phynode *
143phynode_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
170static struct phynode *
171phynode_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
191void *
192phynode_get_softc(struct phynode *phynode)
193{
194
195	return (phynode->softc);
196}
197
198device_t
199phynode_get_device(struct phynode *phynode)
200{
201
202	return (phynode->pdev);
203}
204
205intptr_t phynode_get_id(struct phynode *phynode)
206{
207
208	return (phynode->id);
209}
210
211#ifdef FDT
212phandle_t
213phynode_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 */
229int
230phynode_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 */
252int
253phynode_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/*
274 * Get phy status. (PHY_STATUS_*)
275 */
276int
277phynode_status(struct phynode *phynode, int *status)
278{
279	int rv;
280
281	PHY_TOPO_ASSERT();
282
283	PHYNODE_XLOCK(phynode);
284	rv = PHYNODE_STATUS(phynode, status);
285	PHYNODE_UNLOCK(phynode);
286	return (rv);
287}
288
289 /* --------------------------------------------------------------------------
290 *
291 * Phy consumers interface.
292 *
293 */
294
295/* Helper function for phy_get*() */
296static phy_t
297phy_create(struct phynode *phynode, device_t cdev)
298{
299	struct phy *phy;
300
301	PHY_TOPO_ASSERT();
302
303	phy =  malloc(sizeof(struct phy), M_PHY, M_WAITOK | M_ZERO);
304	phy->cdev = cdev;
305	phy->phynode = phynode;
306	phy->enable_cnt = 0;
307
308	PHYNODE_XLOCK(phynode);
309	phynode->ref_cnt++;
310	TAILQ_INSERT_TAIL(&phynode->consumers_list, phy, link);
311	PHYNODE_UNLOCK(phynode);
312
313	return (phy);
314}
315
316int
317phy_enable(phy_t phy)
318{
319	int rv;
320	struct phynode *phynode;
321
322	phynode = phy->phynode;
323	KASSERT(phynode->ref_cnt > 0,
324	    ("Attempt to access unreferenced phy.\n"));
325
326	PHY_TOPO_SLOCK();
327	rv = phynode_enable(phynode);
328	if (rv == 0)
329		phy->enable_cnt++;
330	PHY_TOPO_UNLOCK();
331	return (rv);
332}
333
334int
335phy_disable(phy_t phy)
336{
337	int rv;
338	struct phynode *phynode;
339
340	phynode = phy->phynode;
341	KASSERT(phynode->ref_cnt > 0,
342	   ("Attempt to access unreferenced phy.\n"));
343	KASSERT(phy->enable_cnt > 0,
344	   ("Attempt to disable already disabled phy.\n"));
345
346	PHY_TOPO_SLOCK();
347	rv = phynode_disable(phynode);
348	if (rv == 0)
349		phy->enable_cnt--;
350	PHY_TOPO_UNLOCK();
351	return (rv);
352}
353
354int
355phy_status(phy_t phy, int *status)
356{
357	int rv;
358	struct phynode *phynode;
359
360	phynode = phy->phynode;
361	KASSERT(phynode->ref_cnt > 0,
362	   ("Attempt to access unreferenced phy.\n"));
363
364	PHY_TOPO_SLOCK();
365	rv = phynode_status(phynode, status);
366	PHY_TOPO_UNLOCK();
367	return (rv);
368}
369
370int
371phy_get_by_id(device_t consumer_dev, device_t provider_dev, intptr_t id,
372    phy_t *phy)
373{
374	struct phynode *phynode;
375
376	PHY_TOPO_SLOCK();
377
378	phynode = phynode_find_by_id(provider_dev, id);
379	if (phynode == NULL) {
380		PHY_TOPO_UNLOCK();
381		return (ENODEV);
382	}
383	*phy = phy_create(phynode, consumer_dev);
384	PHY_TOPO_UNLOCK();
385
386	return (0);
387}
388
389void
390phy_release(phy_t phy)
391{
392	struct phynode *phynode;
393
394	phynode = phy->phynode;
395	KASSERT(phynode->ref_cnt > 0,
396	   ("Attempt to access unreferenced phy.\n"));
397
398	PHY_TOPO_SLOCK();
399	while (phy->enable_cnt > 0) {
400		phynode_disable(phynode);
401		phy->enable_cnt--;
402	}
403	PHYNODE_XLOCK(phynode);
404	TAILQ_REMOVE(&phynode->consumers_list, phy, link);
405	phynode->ref_cnt--;
406	PHYNODE_UNLOCK(phynode);
407	PHY_TOPO_UNLOCK();
408
409	free(phy, M_PHY);
410}
411
412#ifdef FDT
413int phydev_default_ofw_map(device_t provider, phandle_t xref, int ncells,
414    pcell_t *cells, intptr_t *id)
415{
416	struct phynode *entry;
417	phandle_t node;
418
419	/* Single device can register multiple subnodes. */
420	if (ncells == 0) {
421
422		node = OF_node_from_xref(xref);
423		PHY_TOPO_XLOCK();
424		TAILQ_FOREACH(entry, &phynode_list, phylist_link) {
425			if ((entry->pdev == provider) &&
426			    (entry->ofw_node == node)) {
427				*id = entry->id;
428				PHY_TOPO_UNLOCK();
429				return (0);
430			}
431		}
432		PHY_TOPO_UNLOCK();
433		return (ERANGE);
434	}
435
436	/* First cell is ID. */
437	if (ncells == 1) {
438		*id = cells[0];
439		return (0);
440	}
441
442	/* No default way how to get ID, custom mapper is required. */
443	return  (ERANGE);
444}
445
446int
447phy_get_by_ofw_idx(device_t consumer_dev, phandle_t cnode, int idx, phy_t *phy)
448{
449	phandle_t xnode;
450	pcell_t *cells;
451	device_t phydev;
452	int ncells, rv;
453	intptr_t id;
454
455	if (cnode <= 0)
456		cnode = ofw_bus_get_node(consumer_dev);
457	if (cnode <= 0) {
458		device_printf(consumer_dev,
459		    "%s called on not ofw based device\n", __func__);
460		return (ENXIO);
461	}
462	rv = ofw_bus_parse_xref_list_alloc(cnode, "phys", "#phy-cells", idx,
463	    &xnode, &ncells, &cells);
464	if (rv != 0)
465		return (rv);
466
467	/* Tranlate provider to device. */
468	phydev = OF_device_from_xref(xnode);
469	if (phydev == NULL) {
470		OF_prop_free(cells);
471		return (ENODEV);
472	}
473	/* Map phy to number. */
474	rv = PHYDEV_MAP(phydev, xnode, ncells, cells, &id);
475	OF_prop_free(cells);
476	if (rv != 0)
477		return (rv);
478
479	return (phy_get_by_id(consumer_dev, phydev, id, phy));
480}
481
482int
483phy_get_by_ofw_name(device_t consumer_dev, phandle_t cnode, char *name,
484    phy_t *phy)
485{
486	int rv, idx;
487
488	if (cnode <= 0)
489		cnode = ofw_bus_get_node(consumer_dev);
490	if (cnode <= 0) {
491		device_printf(consumer_dev,
492		    "%s called on not ofw based device\n",  __func__);
493		return (ENXIO);
494	}
495	rv = ofw_bus_find_string_index(cnode, "phy-names", name, &idx);
496	if (rv != 0)
497		return (rv);
498	return (phy_get_by_ofw_idx(consumer_dev, cnode, idx, phy));
499}
500
501int
502phy_get_by_ofw_property(device_t consumer_dev, phandle_t cnode, char *name,
503    phy_t *phy)
504{
505	pcell_t *cells;
506	device_t phydev;
507	int ncells, rv;
508	intptr_t id;
509
510	if (cnode <= 0)
511		cnode = ofw_bus_get_node(consumer_dev);
512	if (cnode <= 0) {
513		device_printf(consumer_dev,
514		    "%s called on not ofw based device\n", __func__);
515		return (ENXIO);
516	}
517	ncells = OF_getencprop_alloc_multi(cnode, name, sizeof(pcell_t),
518	    (void **)&cells);
519	if (ncells < 1)
520		return (ENOENT);
521
522	/* Tranlate provider to device. */
523	phydev = OF_device_from_xref(cells[0]);
524	if (phydev == NULL) {
525		OF_prop_free(cells);
526		return (ENODEV);
527	}
528	/* Map phy to number. */
529	rv = PHYDEV_MAP(phydev, cells[0], ncells - 1 , cells + 1, &id);
530	OF_prop_free(cells);
531	if (rv != 0)
532		return (rv);
533
534	return (phy_get_by_id(consumer_dev, phydev, id, phy));
535}
536#endif
537