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