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
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 * Set phy mode (protocol and its variant).
274 */
275int
276phynode_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 */
292int
293phynode_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*() */
312static phy_t
313phy_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
332int
333phy_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
350int
351phy_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
370int
371phy_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
386int
387phy_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
402int
403phy_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
421void
422phy_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
445int 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
478int
479phy_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
514int
515phy_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
533int
534phy_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