/* Copyright (c) 2015 Broadcom Corporation All Rights Reserved <:label-BRCM:2015:DUAL/GPL:standard Unless you and Broadcom execute a separate written software license agreement governing use of this software, this software is licensed to you under the terms of the GNU General Public License version 2 (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php, with the following added to such license: As a special exception, the copyright holders of this software give you permission to link this software with independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from this software. The special exception does not apply to any modifications of the software. Not withstanding the above, under no circumstances may you combine this software in any way with any other Broadcom software provided under a license other than the GPL, without Broadcom's express prior written consent. :> */ /* * Created on: Jul 2017 * Author: ido.brezel@broadcom.com */ /* * Phy driver wrapper for crossbar */ #include "phy_drv.h" #include "phy_drv_crossbar.h" #include "crossbar_dev_plat.h" #include "brcm_rgmii.h" #include "bcm_bca_leds.h" #define dprintk printk phy_drv_t phy_drv_crossbar; typedef struct { int crossbar_id; int internal_endpoint; phy_dev_t self; phy_dev_t *phys[MAX_PHYS_PER_CROSSBAR_GROUP]; int external_endpoint[MAX_PHYS_PER_CROSSBAR_GROUP]; phy_dev_t *connected_phy; select_cb_t select; bca_leds_info_t *leds_info; } crossbar_group_t; static crossbar_group_t crossbars[MAX_CROSSBAR_GROUPS]; static int crossbar_get_index(phy_dev_t *phy_dev_crossbar) { int i; if (!phy_dev_crossbar) return -1; for (i = 0; i < MAX_CROSSBAR_GROUPS; i++) { if (&crossbars[i].self == phy_dev_crossbar) return i; } return -1; } static crossbar_group_t *crossbar_get(phy_dev_t *phy_dev_crossbar) { int i = crossbar_get_index(phy_dev_crossbar); if (i == -1) return NULL; return &crossbars[i]; } static int crossbar_idx_by_phy(phy_dev_t *phy_dev, int *group_idx, int *phys_idx) { int i, j; if (!phy_dev) return -1; phy_dev = cascade_phy_get_first(phy_dev); for (i = 0; i < MAX_CROSSBAR_GROUPS ; i++) { if (!crossbars[i].self.phy_drv) continue; for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (crossbars[i].phys[j] == phy_dev) { *group_idx = i; *phys_idx = j; return 0; } } } return -1; } /* phy_dev_t *crossbar_group_phy_by_indices(int crossbar_id, int internal_endpoint, int external_endpoint) { int i, j; for (i = 0; i < MAX_CROSSBAR_GROUPS ; i++) { if (!crossbars[i].self.phy_drv || crossbars[i].crossbar_id == crossbar_id) continue; for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (crossbars[i].phys[j] && crossbars[i].external_endpoint[j] == external_endpoint) return crossbars[i].phys[j]; } } return NULL; } EXPORT_SYMBOL(crossbar_group_phy_by_indices); */ phy_dev_t *crossbar_group(int crossbar_id, int internal_endpoint) { int i; for (i = 0; i < MAX_CROSSBAR_GROUPS ; i++) { if (crossbars[i].self.phy_drv && crossbars[i].crossbar_id == crossbar_id && crossbars[i].internal_endpoint == internal_endpoint) return &crossbars[i].self; } return NULL; } EXPORT_SYMBOL(crossbar_group); static crossbar_group_t *crossbar_get_by_phy(phy_dev_t *phy_dev) { int i, j; if (!crossbar_idx_by_phy(phy_dev, &i, &j)) return &crossbars[i]; return NULL; } int crossbar_external_endpoint(phy_dev_t *phy_dev) { int i, j; if (!crossbar_idx_by_phy(phy_dev, &i, &j)) return crossbars[i].external_endpoint[j]; return -1; } EXPORT_SYMBOL(crossbar_external_endpoint); int crossbar_info_by_phy(phy_dev_t *phy_dev, int *crossbar_id, int *internal_endpoint, int *external_endpoint) { int i, j; if (!crossbar_idx_by_phy(phy_dev, &i, &j)) { if (crossbar_id) *crossbar_id = crossbars[i].crossbar_id; if (internal_endpoint) *internal_endpoint = crossbars[i].internal_endpoint; if (external_endpoint) *external_endpoint = crossbars[i].external_endpoint[j]; return 0; } return -1; } EXPORT_SYMBOL(crossbar_info_by_phy); int crossbar_current_status(phy_dev_t *phy_crossbar, int *internal_endpoint, int *external_endpoint) { int i, j; if ((i = crossbar_get_index(phy_crossbar)) == -1) return -1; if (internal_endpoint) *internal_endpoint = crossbars[i].internal_endpoint; j = crossbar_external_endpoint(crossbars[i].connected_phy); if (external_endpoint) *external_endpoint = j; return 0; } EXPORT_SYMBOL(crossbar_current_status); /* * When the active phy's link is up/down, power down/up all other phys in group. */ static void _crossbar_link_powerset(crossbar_group_t *crossbar) { int j; for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (!crossbar->phys[j]) continue; /* Skip connected phy */ if (crossbar->phys[j] == crossbar->connected_phy) continue; phy_dev_power_set(crossbar->phys[j], !crossbar->connected_phy->link); } } static void crossbar_link_change_cb(void *ctx) { phy_dev_t *phy_dev_crossbar, *active_phy = ctx; phy_dev_t *first_phy = cascade_phy_get_first(active_phy); crossbar_group_t *crossbar = crossbar_get_by_phy(first_phy); phy_dev_crossbar = &crossbar->self; /* No change in connected_phy, return */ if (first_phy != crossbar->connected_phy && phy_dev_crossbar->link && active_phy->link) return; phy_dev_crossbar->link = active_phy->link; phy_dev_crossbar->speed = active_phy->speed; phy_dev_crossbar->duplex = active_phy->duplex; phy_dev_crossbar->pause_rx = active_phy->pause_rx; phy_dev_crossbar->pause_tx = active_phy->pause_tx; phy_dev_crossbar->addr = active_phy->addr; phy_dev_crossbar->flag = active_phy->flag; if (crossbar->select && first_phy != crossbar->connected_phy) { crossbar->select(crossbar->crossbar_id, crossbar->internal_endpoint, crossbar_external_endpoint(first_phy), first_phy); crossbar->connected_phy = first_phy; /* in case connected_phy is end_phy */ } if (phy_dev_crossbar->flag & PHY_FLAG_POWER_SET_ENABLED) _crossbar_link_powerset(crossbar); if (phy_dev_crossbar->link_change_cb) phy_dev_crossbar->link_change_cb(phy_dev_crossbar->link_change_ctx); } static int crossbar_set_active_ext_ep_led(crossbar_group_t *crossbar, int ext_ep_num) { bca_leds_info_t *ledinfo; int ret = -1; if (crossbar->leds_info && crossbar->connected_phy) { ledinfo = crossbar->leds_info; /* Assume network-led and network-led-0 shared the first ledinfo network-led and network-led-n never co-exist */ if (ledinfo[ext_ep_num].port_id != 0xff) ledinfo = &ledinfo[ext_ep_num]; ret = crossbar->connected_phy->phy_drv->leds_init(crossbar->connected_phy, ledinfo); } return ret; } static int crossbar_phy_leds_init(phy_dev_t *phy_dev_crossbar, void *leds_info) { crossbar_group_t *crossbar = crossbar_get(phy_dev_crossbar); int ext_ep; if (crossbar) { crossbar->leds_info = leds_info; if (crossbar->connected_phy) { ext_ep = crossbar_external_endpoint(crossbar->connected_phy); if (ext_ep != -1) { crossbar_set_active_ext_ep_led(crossbar, ext_ep); } } return 0; } else return -1; } int phy_drv_crossbar_group_phy_del(phy_dev_t *phy_dev_crossbar, phy_dev_t *phy_dev) { int j; crossbar_group_t *crossbar = crossbar_get(phy_dev_crossbar); if (!crossbar) { printk("crossbar: Failed to find crossbar for phy_dev_crossbar %px\n", phy_dev_crossbar); goto Exit; } for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (crossbar->phys[j] == phy_dev) { // III power down ?? crossbar->connected_phy = crossbar->phys[j] = NULL; dprintk("crossbar[%d]: del cb_idx=%d ext_ep=%d %s:%d\n", crossbar_get_index(&crossbar->self), crossbar->crossbar_id, crossbar->external_endpoint[j], phy_dev->phy_drv->name, phy_dev->addr); // crossbar idx return 0; } } Exit: dprintk("crossbar[%d]: failed to remove phy_dev %s:%d\n", crossbar_get_index(&crossbar->self), phy_dev->phy_drv->name, phy_dev->addr); return -1; } EXPORT_SYMBOL(phy_drv_crossbar_group_phy_del); int phy_drv_crossbar_group_phy_add(phy_dev_t *phy_dev_crossbar, phy_dev_t *phy_dev, int external_endpoint) { int i, j, rc = -1; crossbar_group_t *crossbar = crossbar_get(phy_dev_crossbar); if (!crossbar) { printk("crossbar: Failed to find crossbar for phy_dev_crossbar %px\n", phy_dev_crossbar); goto Exit; } if (!phy_dev) { printk("crossbar: Missing phy_dev %px\n", phy_dev); goto Exit; } if (!crossbar_idx_by_phy(phy_dev, &i, &j)) { printk("crossbar[%d]: phy %px already enslaved to another crossbar group\n", crossbar_get_index(&crossbar->self), phy_dev); goto Exit; } for (i = 0; i < MAX_PHYS_PER_CROSSBAR_GROUP && crossbar->phys[i]; i++); if (i == MAX_PHYS_PER_CROSSBAR_GROUP) { goto Exit; } crossbar->phys[i] = phy_dev; crossbar->external_endpoint[i] = external_endpoint; rc = 0; dprintk("crossbar[%d]: Add cb_idx=%d ext_ep=%d %s:%d\n", crossbar_get_index(&crossbar->self), crossbar->crossbar_id, external_endpoint, phy_dev->phy_drv->name, phy_dev->addr); // crossbar idx Exit: return rc; } EXPORT_SYMBOL(phy_drv_crossbar_group_phy_add); phy_dev_t *phy_drv_crossbar_group_alloc(int crossbar_id, int internal_endpoint, select_cb_t select) { int i; crossbar_group_t *crossbar = NULL; for (i = 0; i < MAX_CROSSBAR_GROUPS; i++) { crossbar = &crossbars[i]; if (crossbar->self.phy_drv && crossbar->crossbar_id == crossbar_id && crossbar->internal_endpoint == internal_endpoint) { printk("crossbar: Alloc failed, internal endpoint %d already registered with another group\n", internal_endpoint); return NULL; } } for (i = 0; i < MAX_CROSSBAR_GROUPS && crossbars[i].self.phy_drv; i++); if (i == MAX_CROSSBAR_GROUPS) { printk("crossbar: Failed to allocate new crossbar, Max crossbars reached\n"); return NULL; } crossbars[i].self.phy_drv = &phy_drv_crossbar; crossbars[i].crossbar_id = crossbar_id; crossbars[i].internal_endpoint = internal_endpoint; crossbars[i].select = select; dprintk("crossbar[%d]: Allocated cb_idx=%d int_ep=%d\n", i, crossbar_id, internal_endpoint); return &crossbars[i].self; } void phy_drv_crossbar_group_list(void) { int i, j; phy_dev_t *phy_dev; printk("|==========================================|\n"); printk("| Id | active | Phy | Bus | Addr |\n"); printk("|==========================================|\n"); for (i = 0; i < MAX_CROSSBAR_GROUPS ; i++) { if (!crossbars[i].self.phy_drv) continue; printk("| %1d | I%1d- | | | |\n", i, crossbars[i].internal_endpoint); for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { phy_dev = crossbars[i].phys[j]; while (phy_dev) { printk("| "); pr_cont("| -E%1d%2s ", crossbars[i].external_endpoint[j], crossbars[i].connected_phy == phy_dev ? "*" : " "); pr_cont("| %7s ", phy_dev->phy_drv->name); pr_cont("| %6s ", phy_dev->mii_type == PHY_MII_TYPE_UNKNOWN ? "" : phy_dev_mii_type_to_str(phy_dev->mii_type)); pr_cont("| 0x%02x ", phy_dev->addr); pr_cont("|\n"); phy_dev = cascade_phy_get_next(phy_dev); // traverse thru cascade phys if available } } printk("|==========================================|\n"); } } EXPORT_SYMBOL(phy_drv_crossbar_group_list); phy_dev_t *crossbar_phy_dev_first(phy_dev_t *phy_dev_crossbar) { int j; crossbar_group_t *crossbar = crossbar_get(phy_dev_crossbar); if (crossbar) { for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (crossbar->phys[j]) return crossbar->phys[j]; } } return NULL; } EXPORT_SYMBOL(crossbar_phy_dev_first); phy_dev_t *crossbar_phy_dev_next(phy_dev_t *phy_dev) { int i, j; for (i = 0; i < MAX_CROSSBAR_GROUPS ; i++) { if (!crossbars[i].self.phy_drv) continue; for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (crossbars[i].phys[j] == phy_dev) { for (j++; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (crossbars[i].phys[j]) return crossbars[i].phys[j]; } return NULL; } } } return NULL; } EXPORT_SYMBOL(crossbar_phy_dev_next); phy_dev_t *crossbar_phy_dev_active(phy_dev_t *phy_dev_crossbar) { crossbar_group_t *crossbar = crossbar_get(phy_dev_crossbar); if (crossbar) return cascade_phy_get_last_active(crossbar->connected_phy); return NULL; } EXPORT_SYMBOL(crossbar_phy_dev_active); int crossbar_phys_in_one_group(phy_dev_t *phy1, phy_dev_t *phy2) { return (crossbar_get_by_phy(phy1) == crossbar_get_by_phy(phy2)); } EXPORT_SYMBOL(crossbar_phys_in_one_group); int crossbar_external_to_internal_endpoint(int crossbar_id, int external_endpoint) { int i, j; for (i = 0; i < MAX_CROSSBAR_GROUPS; i++) { if (crossbars[i].crossbar_id != crossbar_id ) continue; for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (crossbars[i].phys[j] && crossbars[i].external_endpoint[j] == external_endpoint) return crossbars[i].internal_endpoint; } } return -1; } EXPORT_SYMBOL(crossbar_external_to_internal_endpoint); phy_dev_t *crossbar_get_phy_by_type(int phy_type) { int i, j; for (i = 0; i < MAX_CROSSBAR_GROUPS; i++) { for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { phy_dev_t *phy = crossbars[i].phys[j]; if (phy && phy->phy_drv->phy_type == phy_type) return phy; } } return NULL; } EXPORT_SYMBOL(crossbar_get_phy_by_type); int crossbar_group_external_endpoint_count(phy_dev_t *phy_dev_crossbar, int *external_map) { int j, count = 0; int map = 0; crossbar_group_t *crossbar = crossbar_get(phy_dev_crossbar); if (crossbar) { for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) if (crossbar->phys[j]) { count++; map |= 1 << crossbar->external_endpoint[j]; } } else return -1; // not a valid crossbar_phy if (external_map) *external_map = map; return count; } EXPORT_SYMBOL(crossbar_group_external_endpoint_count); phy_dev_t *crossbar_group_phy_get(phy_dev_t *phy_dev_crossbar, int external_endpoint) { crossbar_group_t *crossbar = crossbar_get(phy_dev_crossbar); int j; if (crossbar) { for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) if (crossbar->phys[j] && crossbar->external_endpoint[j] == external_endpoint) return crossbar->phys[j]; } return NULL; } EXPORT_SYMBOL(crossbar_group_phy_get); int crossbar_set_active_external_endpoint(int crossbar_id, int internal_endpoint, int external_endpoint) { int i, j; for (i = 0; i < MAX_CROSSBAR_GROUPS; i++) { if (crossbars[i].crossbar_id == crossbar_id && crossbars[i].internal_endpoint == internal_endpoint) { for (j = 0; j < MAX_PHYS_PER_CROSSBAR_GROUP; j++) { if (crossbars[i].phys[j] && crossbars[i].external_endpoint[j] == external_endpoint) { crossbars[i].connected_phy = crossbars[i].phys[j]; crossbars[i].self.addr = crossbars[i].phys[j]->addr; crossbars[i].self.flag = crossbars[i].phys[j]->flag; crossbar_set_active_ext_ep_led(&crossbars[i], external_endpoint); return 0; } } } } return -1; } EXPORT_SYMBOL(crossbar_set_active_external_endpoint); int crossbar_get_int_ext_mapping(int crossbar_id, int max_internal_endpoint, int max_external_endpoint, int *endpoint_pairs) { int i, j; int ext_unused_map = ( 1<phy_drv->name, phy_dev->addr, phy_dev->priv, phy_dev->phy_drv->_name? #_name:"none");\ cascade_phy_dev_ ## _name args;\ }\ return 0;\ } #define PHY_ONE(_name,do_cascade, proto, args)\ static int PHY_CB(_name) proto\ {\ phy_dev_t *phy_dev = crossbar_phy_dev_active(phy_dev_crossbar);\ if (PHY_CB_DBG) dprintk("crossbar[%d]: ONE(%s) active: %s:%x\n", crossbar_get_index(phy_dev_crossbar), #_name, (phy_dev ? phy_dev->phy_drv->name : "NONE"), phy_dev->addr);\ if (!phy_dev) return 0; /* BUG?? */\ return do_cascade ## phy_dev_ ## _name args;\ } /* Beware which operations operate on ACTIVE or ALL phys */ PHY_ONE(read,, (phy_dev_t *phy_dev_crossbar, uint16_t reg, uint16_t *val), (phy_dev, reg, val)); PHY_ONE(write,, (phy_dev_t *phy_dev_crossbar, uint16_t reg, uint16_t val), (phy_dev, reg, val)); /* III: Should this be only on active ? */ PHY_ALL(power_set, (phy_dev_t *phy_dev_crossbar, int enable), (phy_dev, enable)); PHY_ONE(apd_get,, (phy_dev_t *phy_dev_crossbar, int *enable), (phy_dev, enable)); PHY_ALL(apd_set, (phy_dev_t *phy_dev_crossbar, int enable), (phy_dev, enable)); PHY_ONE(eee_get,, (phy_dev_t *phy_dev_crossbar, int *enable), (phy_dev, enable)); PHY_ALL(eee_set, (phy_dev_t *phy_dev_crossbar, int enable), (phy_dev, enable)); PHY_ONE(eee_resolution_get,, (phy_dev_t *phy_dev_crossbar, int *enable), (phy_dev, enable)); PHY_ONE(read_status,, (phy_dev_t *phy_dev_crossbar), (phy_dev)); PHY_ALL(speed_set, (phy_dev_t *phy_dev_crossbar, phy_speed_t speed, phy_duplex_t duplex), (phy_dev, speed, duplex)); PHY_ONE(caps_get, cascade_, (phy_dev_t *phy_dev_crossbar, int caps_type, uint32_t *caps), (phy_dev, caps_type, caps)); PHY_ALL(caps_set, (phy_dev_t *phy_dev_crossbar, uint32_t caps), (phy_dev, caps)); PHY_ONE(phyid_get,, (phy_dev_t *phy_dev_crossbar, uint32_t *phyid), (phy_dev, phyid)); static int cb_init(phy_dev_t *phy_dev_crossbar) { phy_dev_t *phy_dev = crossbar_phy_dev_first(phy_dev_crossbar); if (PHY_CB_DBG) dprintk("crossbar[%d]: ALL(init)\n", crossbar_get_index(phy_dev_crossbar)); for (; phy_dev; phy_dev = crossbar_phy_dev_next(phy_dev)) { if (PHY_CB_DBG) dprintk(" %s:%x:%px %s\n", phy_dev->phy_drv->name, phy_dev->addr, phy_dev->priv, phy_dev->phy_drv->init? "init":"none"); phy_register_polling_timer(phy_dev, crossbar_link_change_cb); phy_dev_init(phy_dev); } return 0; } PHY_ALL(isolate_phy, (phy_dev_t *phy_dev_crossbar, int isolate), (phy_dev, isolate)); extern phy_mii_type_t dt_get_phy_mode_mii_type(struct device_node *np); static int crossbar_dt_priv(dt_handle_t handle, uint32_t addr, uint32_t phy_mode, void **_priv) { phy_dev_t *cbphy; dt_handle_t phy_hdl; phy_dev_t *phy; uint32_t ext_ep, i; cbphy = phy_drv_crossbar_group_alloc(0, addr, &crossbar_plat_select); if (cbphy) for (i = 0; i < MAX_PHYS_PER_CROSSBAR_GROUP; i++) { phy_hdl = dt_parse_phandle(handle, "phy-handle", i); if (!dt_is_valid(phy_hdl)) break; if (!(phy = phy_drv_find_device(phy_hdl))) { printk("phy_dev probe_defer: %s waiting for phy %s\n", dt_get_name(handle), dt_get_name(phy_hdl)); return -EPROBE_DEFER; } ext_ep = dt_property_read_u32(phy_hdl, "xbar-ext-ep"); if (ext_ep == (uint32_t)(-1)) { printk("Missing xbar-ext-ep\n"); goto Exit; } phy_drv_crossbar_group_phy_add(cbphy, phy, ext_ep); { // for phy devices on crossbar, get mii_type, delay_rx, delay_tx from phy instead of port phy_mii_type_t mii_type; rgmii_params params = {}; mii_type = dt_get_phy_mode_mii_type(phy_hdl); if (mii_type == PHY_MII_TYPE_RGMII) { /* devices (138, 148, 158) with crossbar and multiple instances of RGMII needs to have rgmii-intf specified */ if (!of_property_read_u32(phy_hdl, "rgmii-intf", ¶ms.instance)) { params.delay_rx = of_property_read_bool(phy_hdl, "rx-delay"); params.delay_tx = of_property_read_bool(phy_hdl, "tx-delay"); params.is_1p8v = of_property_read_bool(phy_hdl, "rgmii-1p8v"); params.is_3p3v = of_property_read_bool(phy_hdl, "rgmii-3p3v"); rgmii_attach(¶ms); } } phy_dev_attach(phy, mii_type, !params.delay_rx, !params.delay_tx, params.instance); } } *_priv = (void *)cbphy; Exit: return 0; } phy_dev_t *phy_drv_crossbar_find_device(dt_handle_t dt_handle) { int i; for (i = 0; i < MAX_CROSSBAR_GROUPS; i++) { if (crossbars[i].self.phy_drv && crossbars[i].self.dt_handle == dt_handle) return &crossbars[i].self; } return NULL; } phy_drv_t phy_drv_crossbar = { .phy_type = PHY_TYPE_CROSSBAR, .name = "crossbar", .read = PHY_CB(read), .write = PHY_CB(write), .power_set = PHY_CB(power_set), .apd_get = PHY_CB(apd_get), .apd_set = PHY_CB(apd_set), .eee_get = PHY_CB(eee_get), .eee_set = PHY_CB(eee_set), .eee_resolution_get = PHY_CB(eee_resolution_get), .read_status = PHY_CB(read_status), .speed_set = PHY_CB(speed_set), .caps_get = PHY_CB(caps_get), .caps_set = PHY_CB(caps_set), .phyid_get = PHY_CB(phyid_get), .isolate_phy = PHY_CB(isolate_phy), .init = PHY_CB(init), .dt_priv = crossbar_dt_priv, .leds_init = crossbar_phy_leds_init, };