Loading Documentation/admin-guide/kernel-parameters.rst +6 −0 Original line number Diff line number Diff line Loading @@ -211,3 +211,9 @@ Todo ---- Add more DRM drivers. eipv4= [KNL] Sets ipv4 address at boot up for early ethernet. eipv6= [KNL] Sets ipv6 address at boot up for early ethernet. ermac= [KNL] Sets mac address at boot up for early ethernet. No newline at end of file Documentation/admin-guide/kernel-parameters.txt +6 −0 Original line number Diff line number Diff line Loading @@ -5508,3 +5508,9 @@ memory, and other data can't be written using xmon commands. off xmon is disabled. eipv4= [KNL] Sets ipv4 address at boot up for early ethernet. eipv6= [KNL] Sets ipv6 address at boot up for early ethernet. ermac= [KNL] Sets mac address at boot up for early ethernet. drivers/net/ethernet/stmicro/stmmac/common.h +2 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,8 @@ struct stmmac_extra_stats { unsigned long threshold; unsigned long tx_pkt_n; unsigned long rx_pkt_n; unsigned long q_tx_pkt_n[MTL_MAX_TX_QUEUES]; unsigned long q_rx_pkt_n[MTL_MAX_RX_QUEUES]; unsigned long normal_irq_n; unsigned long rx_normal_irq_n; unsigned long napi_poll; Loading drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c +549 −3 Original line number Diff line number Diff line Loading @@ -13,7 +13,6 @@ #include <linux/mii.h> #include <linux/of_mdio.h> #include <linux/slab.h> #include <linux/ipc_logging.h> #include <linux/poll.h> #include <linux/debugfs.h> #include <linux/dma-iommu.h> Loading @@ -22,6 +21,8 @@ #include <linux/tcp.h> #include <linux/ip.h> #include <linux/ipv6.h> #include <linux/rtnetlink.h> #include "stmmac.h" #include "stmmac_platform.h" #include "dwmac-qcom-ethqos.h" Loading Loading @@ -113,10 +114,15 @@ #define MICREL_LINK_UP_INTR_STATUS BIT(0) bool phy_intr_en; void *ipc_emac_log_ctxt; struct emac_emb_smmu_cb_ctx emac_emb_smmu_ctx = {0}; struct qcom_ethqos *pethqos; static unsigned char dev_addr[ETH_ALEN] = { 0, 0x55, 0x7b, 0xb5, 0x7d, 0xf7}; static struct ip_params pparams = {"", "", "", ""}; static inline unsigned int dwmac_qcom_get_eth_type(unsigned char *buf) { return Loading Loading @@ -283,6 +289,168 @@ int ethqos_handle_prv_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) return ret; } static int __init set_early_ethernet_ipv4(char *ipv4_addr_in) { int ret = 1; pparams.is_valid_ipv4_addr = false; if (!ipv4_addr_in) return ret; strlcpy(pparams.ipv4_addr_str, ipv4_addr_in, sizeof(pparams.ipv4_addr_str)); ETHQOSDBG("Early ethernet IPv4 addr: %s\n", pparams.ipv4_addr_str); ret = in4_pton(pparams.ipv4_addr_str, -1, (u8 *)&pparams.ipv4_addr.s_addr, -1, NULL); if (ret != 1 || pparams.ipv4_addr.s_addr == 0) { ETHQOSERR("Invalid ipv4 address programmed: %s\n", ipv4_addr_in); return ret; } pparams.is_valid_ipv4_addr = true; return ret; } __setup("eipv4=", set_early_ethernet_ipv4); static int __init set_early_ethernet_ipv6(char *ipv6_addr_in) { int ret = 1; pparams.is_valid_ipv6_addr = false; if (!ipv6_addr_in) return ret; strlcpy(pparams.ipv6_addr_str, ipv6_addr_in, sizeof(pparams.ipv6_addr_str)); ETHQOSDBG("Early ethernet IPv6 addr: %s\n", pparams.ipv6_addr_str); ret = in6_pton(pparams.ipv6_addr_str, -1, (u8 *)&pparams.ipv6_addr.ifr6_addr.s6_addr32, -1, NULL); if (ret != 1 || !pparams.ipv6_addr.ifr6_addr.s6_addr32) { ETHQOSERR("Invalid ipv6 address programmed: %s\n", ipv6_addr_in); return ret; } pparams.is_valid_ipv6_addr = true; return ret; } __setup("eipv6=", set_early_ethernet_ipv6); static int __init set_early_ethernet_mac(char *mac_addr) { bool valid_mac = false; pparams.is_valid_mac_addr = false; if (!mac_addr) return 1; valid_mac = mac_pton(mac_addr, pparams.mac_addr); if (!valid_mac) goto fail; valid_mac = is_valid_ether_addr(pparams.mac_addr); if (!valid_mac) goto fail; pparams.is_valid_mac_addr = true; return 0; fail: ETHQOSERR("Invalid Mac address programmed: %s\n", mac_addr); return 1; } __setup("ermac=", set_early_ethernet_mac); static int qcom_ethqos_add_ipaddr(struct ip_params *ip_info, struct net_device *dev) { int res = 0; struct ifreq ir; struct sockaddr_in *sin = (void *)&ir.ifr_ifru.ifru_addr; struct net *net = dev_net(dev); if (!net || !net->genl_sock || !net->genl_sock->sk_socket) { ETHQOSINFO("Sock is null, unable to assign ipv4 address\n"); return res; } /*For valid Ipv4 address*/ memset(&ir, 0, sizeof(ir)); memcpy(&sin->sin_addr.s_addr, &ip_info->ipv4_addr, sizeof(sin->sin_addr.s_addr)); strlcpy(ir.ifr_ifrn.ifrn_name, dev->name, sizeof(ir.ifr_ifrn.ifrn_name)); sin->sin_family = AF_INET; sin->sin_port = 0; res = inet_ioctl(net->genl_sock->sk_socket, SIOCSIFADDR, (unsigned long)(void *)&ir); if (res) { ETHQOSERR("can't setup IPv4 address!: %d\r\n", res); } else { ETHQOSINFO("Assigned IPv4 address: %s\r\n", ip_info->ipv4_addr_str); #ifdef CONFIG_MSM_BOOT_TIME_MARKER place_marker("M - Etherent Assigned IPv4 address"); #endif } return res; } static int qcom_ethqos_add_ipv6addr(struct ip_params *ip_info, struct net_device *dev) { int ret = -EFAULT; struct in6_ifreq ir6; char *prefix; struct net *net = dev_net(dev); /*For valid IPv6 address*/ if (!net || !net->genl_sock || !net->genl_sock->sk_socket) ETHQOSERR("Sock is null, unable to assign ipv6 address\n"); if (!net->ipv6.devconf_dflt) { ETHQOSERR("ipv6.devconf_dflt is null, schedule wq\n"); schedule_delayed_work(&pethqos->ipv6_addr_assign_wq, msecs_to_jiffies(1000)); return ret; } memset(&ir6, 0, sizeof(ir6)); memcpy(&ir6, &ip_info->ipv6_addr, sizeof(struct in6_ifreq)); ir6.ifr6_ifindex = dev->ifindex; prefix = strnchr(ip_info->ipv6_addr_str, strlen(ip_info->ipv6_addr_str), '/'); if (!prefix) { ir6.ifr6_prefixlen = 0; } else { kstrtoul(prefix + 1, 0, (unsigned long *)&ir6.ifr6_prefixlen); if (ir6.ifr6_prefixlen > 128) ir6.ifr6_prefixlen = 0; } ret = inet6_ioctl(net->genl_sock->sk_socket, SIOCSIFADDR, (unsigned long)(void *)&ir6); if (ret) { ETHQOSDBG("Can't setup IPv6 address!\r\n"); } else { ETHQOSDBG("Assigned IPv6 address: %s\r\n", ip_info->ipv6_addr_str); #ifdef CONFIG_MSM_BOOT_TIME_MARKER place_marker("M - Ethernet Assigned IPv6 address"); #endif } return ret; } static int rgmii_readl(struct qcom_ethqos *ethqos, unsigned int offset) { return readl(ethqos->rgmii_base + offset); Loading Loading @@ -1048,6 +1216,293 @@ void qcom_ethqos_request_phy_wol(struct plat_stmmacenet_data *plat) } } static void qcom_ethqos_bringup_iface(struct work_struct *work) { struct platform_device *pdev = NULL; struct net_device *ndev = NULL; struct qcom_ethqos *ethqos = container_of(work, struct qcom_ethqos, early_eth); ETHQOSINFO("entry\n"); if (!ethqos) return; pdev = ethqos->pdev; if (!pdev) return; ndev = platform_get_drvdata(pdev); if (!ndev || netif_running(ndev)) return; rtnl_lock(); if (dev_change_flags(ndev, ndev->flags | IFF_UP, NULL) < 0) ETHQOSINFO("ERROR\n"); rtnl_unlock(); ETHQOSINFO("exit\n"); } static void ethqos_is_ipv4_NW_stack_ready(struct work_struct *work) { struct delayed_work *dwork; struct qcom_ethqos *ethqos; struct platform_device *pdev = NULL; struct net_device *ndev = NULL; int ret; ETHQOSINFO("\n"); dwork = container_of(work, struct delayed_work, work); ethqos = container_of(dwork, struct qcom_ethqos, ipv4_addr_assign_wq); if (!ethqos) return; pdev = ethqos->pdev; if (!pdev) return; ndev = platform_get_drvdata(pdev); ret = qcom_ethqos_add_ipaddr(&pparams, ndev); if (ret) return; cancel_delayed_work_sync(ðqos->ipv4_addr_assign_wq); flush_delayed_work(ðqos->ipv4_addr_assign_wq); } static void ethqos_is_ipv6_NW_stack_ready(struct work_struct *work) { struct delayed_work *dwork; struct qcom_ethqos *ethqos; struct platform_device *pdev = NULL; struct net_device *ndev = NULL; int ret; ETHQOSINFO("\n"); dwork = container_of(work, struct delayed_work, work); ethqos = container_of(dwork, struct qcom_ethqos, ipv6_addr_assign_wq); if (!ethqos) return; pdev = ethqos->pdev; if (!pdev) return; ndev = platform_get_drvdata(pdev); ret = qcom_ethqos_add_ipv6addr(&pparams, ndev); if (ret) return; cancel_delayed_work_sync(ðqos->ipv6_addr_assign_wq); flush_delayed_work(ðqos->ipv6_addr_assign_wq); } static int ethqos_set_early_eth_param(struct stmmac_priv *priv, struct qcom_ethqos *ethqos) { int ret = 0; if (priv->plat && priv->plat->mdio_bus_data) priv->plat->mdio_bus_data->phy_mask = priv->plat->mdio_bus_data->phy_mask | DUPLEX_FULL | SPEED_100; priv->early_eth = ethqos->early_eth_enabled; qcom_ethqos_add_ipaddr(&pparams, priv->dev); if (pparams.is_valid_ipv4_addr) { INIT_DELAYED_WORK(ðqos->ipv4_addr_assign_wq, ethqos_is_ipv4_NW_stack_ready); ret = qcom_ethqos_add_ipaddr(&pparams, priv->dev); if (ret) schedule_delayed_work(ðqos->ipv4_addr_assign_wq, msecs_to_jiffies(1000)); } if (pparams.is_valid_ipv6_addr) { INIT_DELAYED_WORK(ðqos->ipv6_addr_assign_wq, ethqos_is_ipv6_NW_stack_ready); ret = qcom_ethqos_add_ipv6addr(&pparams, priv->dev); if (ret) schedule_delayed_work(ðqos->ipv6_addr_assign_wq, msecs_to_jiffies(1000)); } if (pparams.is_valid_mac_addr) { ether_addr_copy(dev_addr, pparams.mac_addr); memcpy(priv->dev->dev_addr, dev_addr, ETH_ALEN); } return ret; } static ssize_t read_phy_reg_dump(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct qcom_ethqos *ethqos = file->private_data; unsigned int len = 0, buf_len = 2000; char *buf; ssize_t ret_cnt; int phydata = 0; int i = 0; struct platform_device *pdev = ethqos->pdev; struct net_device *dev = platform_get_drvdata(pdev); struct stmmac_priv *priv = netdev_priv(dev); if (!ethqos || !dev->phydev) { ETHQOSERR("NULL Pointer\n"); return -EINVAL; } buf = kzalloc(buf_len, GFP_KERNEL); if (!buf) return -ENOMEM; len += scnprintf(buf + len, buf_len - len, "\n************* PHY Reg dump *************\n"); for (i = 0; i < 32; i++) { phydata = ethqos_mdio_read(priv, priv->plat->phy_addr, i); len += scnprintf(buf + len, buf_len - len, "MII Register (%#x) = %#x\n", i, phydata); } if (len > buf_len) { ETHQOSERR("(len > buf_len) buffer not sufficient\n"); len = buf_len; } ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); kfree(buf); return ret_cnt; } static ssize_t read_rgmii_reg_dump(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct qcom_ethqos *ethqos = file->private_data; unsigned int len = 0, buf_len = 2000; char *buf; ssize_t ret_cnt; int rgmii_data = 0; struct platform_device *pdev = ethqos->pdev; struct net_device *dev = platform_get_drvdata(pdev); if (!ethqos || !dev->phydev) { ETHQOSERR("NULL Pointer\n"); return -EINVAL; } buf = kzalloc(buf_len, GFP_KERNEL); if (!buf) return -ENOMEM; len += scnprintf(buf + len, buf_len - len, "\n************* RGMII Reg dump *************\n"); rgmii_data = rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG); len += scnprintf(buf + len, buf_len - len, "RGMII_IO_MACRO_CONFIG Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG); len += scnprintf(buf + len, buf_len - len, "SDCC_HC_REG_DLL_CONFIG Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDCC_HC_REG_DDR_CONFIG); len += scnprintf(buf + len, buf_len - len, "SDCC_HC_REG_DDR_CONFIG Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG2); len += scnprintf(buf + len, buf_len - len, "SDCC_HC_REG_DLL_CONFIG2 Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDC4_STATUS); len += scnprintf(buf + len, buf_len - len, "SDC4_STATUS Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDCC_USR_CTL); len += scnprintf(buf + len, buf_len - len, "SDCC_USR_CTL Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG2); len += scnprintf(buf + len, buf_len - len, "RGMII_IO_MACRO_CONFIG2 Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, RGMII_IO_MACRO_DEBUG1); len += scnprintf(buf + len, buf_len - len, "RGMII_IO_MACRO_DEBUG1 Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, EMAC_SYSTEM_LOW_POWER_DEBUG); len += scnprintf(buf + len, buf_len - len, "EMAC_SYSTEM_LOW_POWER_DEBUG Register = %#x\n", rgmii_data); if (len > buf_len) { ETHQOSERR("(len > buf_len) buffer not sufficient\n"); len = buf_len; } ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); kfree(buf); return ret_cnt; } static const struct file_operations fops_phy_reg_dump = { .read = read_phy_reg_dump, .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; static const struct file_operations fops_rgmii_reg_dump = { .read = read_rgmii_reg_dump, .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; static int ethqos_create_debugfs(struct qcom_ethqos *ethqos) { static struct dentry *phy_reg_dump; static struct dentry *rgmii_reg_dump; if (!ethqos) { ETHQOSERR("Null Param %s\n", __func__); return -ENOMEM; } ethqos->debugfs_dir = debugfs_create_dir("eth", NULL); if (!ethqos->debugfs_dir || IS_ERR(ethqos->debugfs_dir)) { ETHQOSERR("Can't create debugfs dir\n"); return -ENOMEM; } phy_reg_dump = debugfs_create_file("phy_reg_dump", 0400, ethqos->debugfs_dir, ethqos, &fops_phy_reg_dump); if (!phy_reg_dump || IS_ERR(phy_reg_dump)) { ETHQOSERR("Can't create phy_dump %p\n", phy_reg_dump); goto fail; } rgmii_reg_dump = debugfs_create_file("rgmii_reg_dump", 0400, ethqos->debugfs_dir, ethqos, &fops_rgmii_reg_dump); if (!rgmii_reg_dump || IS_ERR(rgmii_reg_dump)) { ETHQOSERR("Can't create rgmii_dump %p\n", rgmii_reg_dump); goto fail; } return 0; fail: debugfs_remove_recursive(ethqos->debugfs_dir); return -ENOMEM; } static int qcom_ethqos_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; Loading @@ -1056,10 +1511,23 @@ static int qcom_ethqos_probe(struct platform_device *pdev) struct qcom_ethqos *ethqos = NULL; struct resource *res = NULL; int ret; struct net_device *ndev; struct stmmac_priv *priv; if (of_device_is_compatible(pdev->dev.of_node, "qcom,emac-smmu-embedded")) return emac_emb_smmu_cb_probe(pdev); #ifdef CONFIG_MSM_BOOT_TIME_MARKER place_marker("M - Ethernet probe start"); #endif ipc_emac_log_ctxt = ipc_log_context_create(IPCLOG_STATE_PAGES, "emac", 0); if (!ipc_emac_log_ctxt) ETHQOSERR("Error creating logging context for emac\n"); else ETHQOSDBG("IPC logging has been enabled for emac\n"); ret = stmmac_get_platform_resources(pdev, &stmmac_res); if (ret) return ret; Loading @@ -1081,7 +1549,6 @@ static int qcom_ethqos_probe(struct platform_device *pdev) return PTR_ERR(plat_dat); } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rgmii"); ethqos->rgmii_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(ethqos->rgmii_base)) { Loading @@ -1102,6 +1569,22 @@ static int qcom_ethqos_probe(struct platform_device *pdev) if (ret) goto err_mem; /*Initialize Early ethernet to false*/ ethqos->early_eth_enabled = false; /*Check for valid mac, ip address to enable Early eth*/ if (pparams.is_valid_mac_addr && (pparams.is_valid_ipv4_addr || pparams.is_valid_ipv6_addr)) { /* For 1000BASE-T mode, auto-negotiation is required and * always used to establish a link. * Configure phy and MAC in 100Mbps mode with autoneg * disable as link up takes more time with autoneg * enabled. */ ethqos->early_eth_enabled = true; ETHQOSINFO("Early ethernet is enabled\n"); } ethqos->speed = SPEED_10; ethqos_update_rgmii_clk_and_bus_cfg(ethqos, SPEED_10); ethqos_set_func_clk_en(ethqos); Loading Loading @@ -1168,6 +1651,20 @@ static int qcom_ethqos_probe(struct platform_device *pdev) } pethqos = ethqos; ethqos_create_debugfs(ethqos); ndev = dev_get_drvdata(ðqos->pdev->dev); priv = netdev_priv(ndev); if (ethqos->early_eth_enabled) { /* Initialize work*/ INIT_WORK(ðqos->early_eth, qcom_ethqos_bringup_iface); /* Queue the work*/ queue_work(system_wq, ðqos->early_eth); /*Set early eth parameters*/ ethqos_set_early_eth_param(priv, ethqos); } return ret; err_clk: Loading Loading @@ -1276,7 +1773,56 @@ static struct platform_driver qcom_ethqos_driver = { .of_match_table = of_match_ptr(qcom_ethqos_match), }, }; module_platform_driver(qcom_ethqos_driver); static int __init qcom_ethqos_init_module(void) { int ret = 0; ETHQOSINFO("\n"); ret = platform_driver_register(&qcom_ethqos_driver); if (ret < 0) { ETHQOSINFO("qcom-ethqos: Driver registration failed"); return ret; } ETHQOSINFO("\n"); return ret; } static void __exit qcom_ethqos_exit_module(void) { ETHQOSINFO("\n"); platform_driver_unregister(&qcom_ethqos_driver); ETHQOSINFO("\n"); } /*! * \brief Macro to register the driver registration function. * * \details A module always begin with either the init_module or the function * you specify with module_init call. This is the entry function for modules; * it tells the kernel what functionality the module provides and sets up the * kernel to run the module's functions when they're needed. Once it does this, * entry function returns and the module does nothing until the kernel wants * to do something with the code that the module provides. */ module_init(qcom_ethqos_init_module) /*! * \brief Macro to register the driver un-registration function. * * \details All modules end by calling either cleanup_module or the function * you specify with the module_exit call. This is the exit function for modules; * it undoes whatever entry function did. It unregisters the functionality * that the entry function registered. */ module_exit(qcom_ethqos_exit_module) MODULE_DESCRIPTION("Qualcomm ETHQOS driver"); MODULE_LICENSE("GPL v2"); drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.h +49 −1 Original line number Diff line number Diff line Loading @@ -5,12 +5,38 @@ #define _DWMAC_QCOM_ETHQOS_H //#include <linux/msm-bus.h> #include <linux/ipc_logging.h> extern void *ipc_emac_log_ctxt; #define IPCLOG_STATE_PAGES 50 #define __FILENAME__ (strrchr(__FILE__, '/') ? \ strrchr(__FILE__, '/') + 1 : __FILE__) #include <linux/inetdevice.h> #include <linux/inet.h> #include <net/addrconf.h> #include <net/ipv6.h> #include <net/inet_common.h> #include <linux/uaccess.h> #define QCOM_ETH_QOS_MAC_ADDR_LEN 6 #define QCOM_ETH_QOS_MAC_ADDR_STR_LEN 18 #define DRV_NAME "qcom-ethqos" #define ETHQOSDBG(fmt, args...) \ pr_debug(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args) #define ETHQOSERR(fmt, args...) \ pr_err(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args) do {\ pr_err(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args);\ if (ipc_emac_log_ctxt) { \ ipc_log_string(ipc_emac_log_ctxt, \ "%s: %s[%u]:[emac] ERROR:" fmt, __FILENAME__,\ __func__, __LINE__, ## args); \ } \ } while (0) #define ETHQOSINFO(fmt, args...) \ pr_info(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args) #define RGMII_IO_MACRO_CONFIG 0x0 Loading Loading @@ -162,6 +188,15 @@ struct qcom_ethqos { int clks_suspended; /* Structure which holds done and wait members */ struct completion clk_enable_done; /* early ethernet parameters */ struct work_struct early_eth; struct delayed_work ipv4_addr_assign_wq; struct delayed_work ipv6_addr_assign_wq; bool early_eth_enabled; /* Key Performance Indicators */ bool print_kpi; struct dentry *debugfs_dir; }; struct pps_cfg { Loading Loading @@ -191,6 +226,19 @@ struct pps_info { int channel_no; }; struct ip_params { unsigned char mac_addr[QCOM_ETH_QOS_MAC_ADDR_LEN]; bool is_valid_mac_addr; char link_speed[32]; bool is_valid_link_speed; char ipv4_addr_str[32]; struct in_addr ipv4_addr; bool is_valid_ipv4_addr; char ipv6_addr_str[48]; struct in6_ifreq ipv6_addr; bool is_valid_ipv6_addr; }; int ethqos_init_reqgulators(struct qcom_ethqos *ethqos); void ethqos_disable_regulators(struct qcom_ethqos *ethqos); int ethqos_init_gpio(struct qcom_ethqos *ethqos); Loading Loading
Documentation/admin-guide/kernel-parameters.rst +6 −0 Original line number Diff line number Diff line Loading @@ -211,3 +211,9 @@ Todo ---- Add more DRM drivers. eipv4= [KNL] Sets ipv4 address at boot up for early ethernet. eipv6= [KNL] Sets ipv6 address at boot up for early ethernet. ermac= [KNL] Sets mac address at boot up for early ethernet. No newline at end of file
Documentation/admin-guide/kernel-parameters.txt +6 −0 Original line number Diff line number Diff line Loading @@ -5508,3 +5508,9 @@ memory, and other data can't be written using xmon commands. off xmon is disabled. eipv4= [KNL] Sets ipv4 address at boot up for early ethernet. eipv6= [KNL] Sets ipv6 address at boot up for early ethernet. ermac= [KNL] Sets mac address at boot up for early ethernet.
drivers/net/ethernet/stmicro/stmmac/common.h +2 −0 Original line number Diff line number Diff line Loading @@ -91,6 +91,8 @@ struct stmmac_extra_stats { unsigned long threshold; unsigned long tx_pkt_n; unsigned long rx_pkt_n; unsigned long q_tx_pkt_n[MTL_MAX_TX_QUEUES]; unsigned long q_rx_pkt_n[MTL_MAX_RX_QUEUES]; unsigned long normal_irq_n; unsigned long rx_normal_irq_n; unsigned long napi_poll; Loading
drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.c +549 −3 Original line number Diff line number Diff line Loading @@ -13,7 +13,6 @@ #include <linux/mii.h> #include <linux/of_mdio.h> #include <linux/slab.h> #include <linux/ipc_logging.h> #include <linux/poll.h> #include <linux/debugfs.h> #include <linux/dma-iommu.h> Loading @@ -22,6 +21,8 @@ #include <linux/tcp.h> #include <linux/ip.h> #include <linux/ipv6.h> #include <linux/rtnetlink.h> #include "stmmac.h" #include "stmmac_platform.h" #include "dwmac-qcom-ethqos.h" Loading Loading @@ -113,10 +114,15 @@ #define MICREL_LINK_UP_INTR_STATUS BIT(0) bool phy_intr_en; void *ipc_emac_log_ctxt; struct emac_emb_smmu_cb_ctx emac_emb_smmu_ctx = {0}; struct qcom_ethqos *pethqos; static unsigned char dev_addr[ETH_ALEN] = { 0, 0x55, 0x7b, 0xb5, 0x7d, 0xf7}; static struct ip_params pparams = {"", "", "", ""}; static inline unsigned int dwmac_qcom_get_eth_type(unsigned char *buf) { return Loading Loading @@ -283,6 +289,168 @@ int ethqos_handle_prv_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) return ret; } static int __init set_early_ethernet_ipv4(char *ipv4_addr_in) { int ret = 1; pparams.is_valid_ipv4_addr = false; if (!ipv4_addr_in) return ret; strlcpy(pparams.ipv4_addr_str, ipv4_addr_in, sizeof(pparams.ipv4_addr_str)); ETHQOSDBG("Early ethernet IPv4 addr: %s\n", pparams.ipv4_addr_str); ret = in4_pton(pparams.ipv4_addr_str, -1, (u8 *)&pparams.ipv4_addr.s_addr, -1, NULL); if (ret != 1 || pparams.ipv4_addr.s_addr == 0) { ETHQOSERR("Invalid ipv4 address programmed: %s\n", ipv4_addr_in); return ret; } pparams.is_valid_ipv4_addr = true; return ret; } __setup("eipv4=", set_early_ethernet_ipv4); static int __init set_early_ethernet_ipv6(char *ipv6_addr_in) { int ret = 1; pparams.is_valid_ipv6_addr = false; if (!ipv6_addr_in) return ret; strlcpy(pparams.ipv6_addr_str, ipv6_addr_in, sizeof(pparams.ipv6_addr_str)); ETHQOSDBG("Early ethernet IPv6 addr: %s\n", pparams.ipv6_addr_str); ret = in6_pton(pparams.ipv6_addr_str, -1, (u8 *)&pparams.ipv6_addr.ifr6_addr.s6_addr32, -1, NULL); if (ret != 1 || !pparams.ipv6_addr.ifr6_addr.s6_addr32) { ETHQOSERR("Invalid ipv6 address programmed: %s\n", ipv6_addr_in); return ret; } pparams.is_valid_ipv6_addr = true; return ret; } __setup("eipv6=", set_early_ethernet_ipv6); static int __init set_early_ethernet_mac(char *mac_addr) { bool valid_mac = false; pparams.is_valid_mac_addr = false; if (!mac_addr) return 1; valid_mac = mac_pton(mac_addr, pparams.mac_addr); if (!valid_mac) goto fail; valid_mac = is_valid_ether_addr(pparams.mac_addr); if (!valid_mac) goto fail; pparams.is_valid_mac_addr = true; return 0; fail: ETHQOSERR("Invalid Mac address programmed: %s\n", mac_addr); return 1; } __setup("ermac=", set_early_ethernet_mac); static int qcom_ethqos_add_ipaddr(struct ip_params *ip_info, struct net_device *dev) { int res = 0; struct ifreq ir; struct sockaddr_in *sin = (void *)&ir.ifr_ifru.ifru_addr; struct net *net = dev_net(dev); if (!net || !net->genl_sock || !net->genl_sock->sk_socket) { ETHQOSINFO("Sock is null, unable to assign ipv4 address\n"); return res; } /*For valid Ipv4 address*/ memset(&ir, 0, sizeof(ir)); memcpy(&sin->sin_addr.s_addr, &ip_info->ipv4_addr, sizeof(sin->sin_addr.s_addr)); strlcpy(ir.ifr_ifrn.ifrn_name, dev->name, sizeof(ir.ifr_ifrn.ifrn_name)); sin->sin_family = AF_INET; sin->sin_port = 0; res = inet_ioctl(net->genl_sock->sk_socket, SIOCSIFADDR, (unsigned long)(void *)&ir); if (res) { ETHQOSERR("can't setup IPv4 address!: %d\r\n", res); } else { ETHQOSINFO("Assigned IPv4 address: %s\r\n", ip_info->ipv4_addr_str); #ifdef CONFIG_MSM_BOOT_TIME_MARKER place_marker("M - Etherent Assigned IPv4 address"); #endif } return res; } static int qcom_ethqos_add_ipv6addr(struct ip_params *ip_info, struct net_device *dev) { int ret = -EFAULT; struct in6_ifreq ir6; char *prefix; struct net *net = dev_net(dev); /*For valid IPv6 address*/ if (!net || !net->genl_sock || !net->genl_sock->sk_socket) ETHQOSERR("Sock is null, unable to assign ipv6 address\n"); if (!net->ipv6.devconf_dflt) { ETHQOSERR("ipv6.devconf_dflt is null, schedule wq\n"); schedule_delayed_work(&pethqos->ipv6_addr_assign_wq, msecs_to_jiffies(1000)); return ret; } memset(&ir6, 0, sizeof(ir6)); memcpy(&ir6, &ip_info->ipv6_addr, sizeof(struct in6_ifreq)); ir6.ifr6_ifindex = dev->ifindex; prefix = strnchr(ip_info->ipv6_addr_str, strlen(ip_info->ipv6_addr_str), '/'); if (!prefix) { ir6.ifr6_prefixlen = 0; } else { kstrtoul(prefix + 1, 0, (unsigned long *)&ir6.ifr6_prefixlen); if (ir6.ifr6_prefixlen > 128) ir6.ifr6_prefixlen = 0; } ret = inet6_ioctl(net->genl_sock->sk_socket, SIOCSIFADDR, (unsigned long)(void *)&ir6); if (ret) { ETHQOSDBG("Can't setup IPv6 address!\r\n"); } else { ETHQOSDBG("Assigned IPv6 address: %s\r\n", ip_info->ipv6_addr_str); #ifdef CONFIG_MSM_BOOT_TIME_MARKER place_marker("M - Ethernet Assigned IPv6 address"); #endif } return ret; } static int rgmii_readl(struct qcom_ethqos *ethqos, unsigned int offset) { return readl(ethqos->rgmii_base + offset); Loading Loading @@ -1048,6 +1216,293 @@ void qcom_ethqos_request_phy_wol(struct plat_stmmacenet_data *plat) } } static void qcom_ethqos_bringup_iface(struct work_struct *work) { struct platform_device *pdev = NULL; struct net_device *ndev = NULL; struct qcom_ethqos *ethqos = container_of(work, struct qcom_ethqos, early_eth); ETHQOSINFO("entry\n"); if (!ethqos) return; pdev = ethqos->pdev; if (!pdev) return; ndev = platform_get_drvdata(pdev); if (!ndev || netif_running(ndev)) return; rtnl_lock(); if (dev_change_flags(ndev, ndev->flags | IFF_UP, NULL) < 0) ETHQOSINFO("ERROR\n"); rtnl_unlock(); ETHQOSINFO("exit\n"); } static void ethqos_is_ipv4_NW_stack_ready(struct work_struct *work) { struct delayed_work *dwork; struct qcom_ethqos *ethqos; struct platform_device *pdev = NULL; struct net_device *ndev = NULL; int ret; ETHQOSINFO("\n"); dwork = container_of(work, struct delayed_work, work); ethqos = container_of(dwork, struct qcom_ethqos, ipv4_addr_assign_wq); if (!ethqos) return; pdev = ethqos->pdev; if (!pdev) return; ndev = platform_get_drvdata(pdev); ret = qcom_ethqos_add_ipaddr(&pparams, ndev); if (ret) return; cancel_delayed_work_sync(ðqos->ipv4_addr_assign_wq); flush_delayed_work(ðqos->ipv4_addr_assign_wq); } static void ethqos_is_ipv6_NW_stack_ready(struct work_struct *work) { struct delayed_work *dwork; struct qcom_ethqos *ethqos; struct platform_device *pdev = NULL; struct net_device *ndev = NULL; int ret; ETHQOSINFO("\n"); dwork = container_of(work, struct delayed_work, work); ethqos = container_of(dwork, struct qcom_ethqos, ipv6_addr_assign_wq); if (!ethqos) return; pdev = ethqos->pdev; if (!pdev) return; ndev = platform_get_drvdata(pdev); ret = qcom_ethqos_add_ipv6addr(&pparams, ndev); if (ret) return; cancel_delayed_work_sync(ðqos->ipv6_addr_assign_wq); flush_delayed_work(ðqos->ipv6_addr_assign_wq); } static int ethqos_set_early_eth_param(struct stmmac_priv *priv, struct qcom_ethqos *ethqos) { int ret = 0; if (priv->plat && priv->plat->mdio_bus_data) priv->plat->mdio_bus_data->phy_mask = priv->plat->mdio_bus_data->phy_mask | DUPLEX_FULL | SPEED_100; priv->early_eth = ethqos->early_eth_enabled; qcom_ethqos_add_ipaddr(&pparams, priv->dev); if (pparams.is_valid_ipv4_addr) { INIT_DELAYED_WORK(ðqos->ipv4_addr_assign_wq, ethqos_is_ipv4_NW_stack_ready); ret = qcom_ethqos_add_ipaddr(&pparams, priv->dev); if (ret) schedule_delayed_work(ðqos->ipv4_addr_assign_wq, msecs_to_jiffies(1000)); } if (pparams.is_valid_ipv6_addr) { INIT_DELAYED_WORK(ðqos->ipv6_addr_assign_wq, ethqos_is_ipv6_NW_stack_ready); ret = qcom_ethqos_add_ipv6addr(&pparams, priv->dev); if (ret) schedule_delayed_work(ðqos->ipv6_addr_assign_wq, msecs_to_jiffies(1000)); } if (pparams.is_valid_mac_addr) { ether_addr_copy(dev_addr, pparams.mac_addr); memcpy(priv->dev->dev_addr, dev_addr, ETH_ALEN); } return ret; } static ssize_t read_phy_reg_dump(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct qcom_ethqos *ethqos = file->private_data; unsigned int len = 0, buf_len = 2000; char *buf; ssize_t ret_cnt; int phydata = 0; int i = 0; struct platform_device *pdev = ethqos->pdev; struct net_device *dev = platform_get_drvdata(pdev); struct stmmac_priv *priv = netdev_priv(dev); if (!ethqos || !dev->phydev) { ETHQOSERR("NULL Pointer\n"); return -EINVAL; } buf = kzalloc(buf_len, GFP_KERNEL); if (!buf) return -ENOMEM; len += scnprintf(buf + len, buf_len - len, "\n************* PHY Reg dump *************\n"); for (i = 0; i < 32; i++) { phydata = ethqos_mdio_read(priv, priv->plat->phy_addr, i); len += scnprintf(buf + len, buf_len - len, "MII Register (%#x) = %#x\n", i, phydata); } if (len > buf_len) { ETHQOSERR("(len > buf_len) buffer not sufficient\n"); len = buf_len; } ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); kfree(buf); return ret_cnt; } static ssize_t read_rgmii_reg_dump(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { struct qcom_ethqos *ethqos = file->private_data; unsigned int len = 0, buf_len = 2000; char *buf; ssize_t ret_cnt; int rgmii_data = 0; struct platform_device *pdev = ethqos->pdev; struct net_device *dev = platform_get_drvdata(pdev); if (!ethqos || !dev->phydev) { ETHQOSERR("NULL Pointer\n"); return -EINVAL; } buf = kzalloc(buf_len, GFP_KERNEL); if (!buf) return -ENOMEM; len += scnprintf(buf + len, buf_len - len, "\n************* RGMII Reg dump *************\n"); rgmii_data = rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG); len += scnprintf(buf + len, buf_len - len, "RGMII_IO_MACRO_CONFIG Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG); len += scnprintf(buf + len, buf_len - len, "SDCC_HC_REG_DLL_CONFIG Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDCC_HC_REG_DDR_CONFIG); len += scnprintf(buf + len, buf_len - len, "SDCC_HC_REG_DDR_CONFIG Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDCC_HC_REG_DLL_CONFIG2); len += scnprintf(buf + len, buf_len - len, "SDCC_HC_REG_DLL_CONFIG2 Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDC4_STATUS); len += scnprintf(buf + len, buf_len - len, "SDC4_STATUS Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, SDCC_USR_CTL); len += scnprintf(buf + len, buf_len - len, "SDCC_USR_CTL Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, RGMII_IO_MACRO_CONFIG2); len += scnprintf(buf + len, buf_len - len, "RGMII_IO_MACRO_CONFIG2 Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, RGMII_IO_MACRO_DEBUG1); len += scnprintf(buf + len, buf_len - len, "RGMII_IO_MACRO_DEBUG1 Register = %#x\n", rgmii_data); rgmii_data = rgmii_readl(ethqos, EMAC_SYSTEM_LOW_POWER_DEBUG); len += scnprintf(buf + len, buf_len - len, "EMAC_SYSTEM_LOW_POWER_DEBUG Register = %#x\n", rgmii_data); if (len > buf_len) { ETHQOSERR("(len > buf_len) buffer not sufficient\n"); len = buf_len; } ret_cnt = simple_read_from_buffer(user_buf, count, ppos, buf, len); kfree(buf); return ret_cnt; } static const struct file_operations fops_phy_reg_dump = { .read = read_phy_reg_dump, .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; static const struct file_operations fops_rgmii_reg_dump = { .read = read_rgmii_reg_dump, .open = simple_open, .owner = THIS_MODULE, .llseek = default_llseek, }; static int ethqos_create_debugfs(struct qcom_ethqos *ethqos) { static struct dentry *phy_reg_dump; static struct dentry *rgmii_reg_dump; if (!ethqos) { ETHQOSERR("Null Param %s\n", __func__); return -ENOMEM; } ethqos->debugfs_dir = debugfs_create_dir("eth", NULL); if (!ethqos->debugfs_dir || IS_ERR(ethqos->debugfs_dir)) { ETHQOSERR("Can't create debugfs dir\n"); return -ENOMEM; } phy_reg_dump = debugfs_create_file("phy_reg_dump", 0400, ethqos->debugfs_dir, ethqos, &fops_phy_reg_dump); if (!phy_reg_dump || IS_ERR(phy_reg_dump)) { ETHQOSERR("Can't create phy_dump %p\n", phy_reg_dump); goto fail; } rgmii_reg_dump = debugfs_create_file("rgmii_reg_dump", 0400, ethqos->debugfs_dir, ethqos, &fops_rgmii_reg_dump); if (!rgmii_reg_dump || IS_ERR(rgmii_reg_dump)) { ETHQOSERR("Can't create rgmii_dump %p\n", rgmii_reg_dump); goto fail; } return 0; fail: debugfs_remove_recursive(ethqos->debugfs_dir); return -ENOMEM; } static int qcom_ethqos_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; Loading @@ -1056,10 +1511,23 @@ static int qcom_ethqos_probe(struct platform_device *pdev) struct qcom_ethqos *ethqos = NULL; struct resource *res = NULL; int ret; struct net_device *ndev; struct stmmac_priv *priv; if (of_device_is_compatible(pdev->dev.of_node, "qcom,emac-smmu-embedded")) return emac_emb_smmu_cb_probe(pdev); #ifdef CONFIG_MSM_BOOT_TIME_MARKER place_marker("M - Ethernet probe start"); #endif ipc_emac_log_ctxt = ipc_log_context_create(IPCLOG_STATE_PAGES, "emac", 0); if (!ipc_emac_log_ctxt) ETHQOSERR("Error creating logging context for emac\n"); else ETHQOSDBG("IPC logging has been enabled for emac\n"); ret = stmmac_get_platform_resources(pdev, &stmmac_res); if (ret) return ret; Loading @@ -1081,7 +1549,6 @@ static int qcom_ethqos_probe(struct platform_device *pdev) return PTR_ERR(plat_dat); } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "rgmii"); ethqos->rgmii_base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(ethqos->rgmii_base)) { Loading @@ -1102,6 +1569,22 @@ static int qcom_ethqos_probe(struct platform_device *pdev) if (ret) goto err_mem; /*Initialize Early ethernet to false*/ ethqos->early_eth_enabled = false; /*Check for valid mac, ip address to enable Early eth*/ if (pparams.is_valid_mac_addr && (pparams.is_valid_ipv4_addr || pparams.is_valid_ipv6_addr)) { /* For 1000BASE-T mode, auto-negotiation is required and * always used to establish a link. * Configure phy and MAC in 100Mbps mode with autoneg * disable as link up takes more time with autoneg * enabled. */ ethqos->early_eth_enabled = true; ETHQOSINFO("Early ethernet is enabled\n"); } ethqos->speed = SPEED_10; ethqos_update_rgmii_clk_and_bus_cfg(ethqos, SPEED_10); ethqos_set_func_clk_en(ethqos); Loading Loading @@ -1168,6 +1651,20 @@ static int qcom_ethqos_probe(struct platform_device *pdev) } pethqos = ethqos; ethqos_create_debugfs(ethqos); ndev = dev_get_drvdata(ðqos->pdev->dev); priv = netdev_priv(ndev); if (ethqos->early_eth_enabled) { /* Initialize work*/ INIT_WORK(ðqos->early_eth, qcom_ethqos_bringup_iface); /* Queue the work*/ queue_work(system_wq, ðqos->early_eth); /*Set early eth parameters*/ ethqos_set_early_eth_param(priv, ethqos); } return ret; err_clk: Loading Loading @@ -1276,7 +1773,56 @@ static struct platform_driver qcom_ethqos_driver = { .of_match_table = of_match_ptr(qcom_ethqos_match), }, }; module_platform_driver(qcom_ethqos_driver); static int __init qcom_ethqos_init_module(void) { int ret = 0; ETHQOSINFO("\n"); ret = platform_driver_register(&qcom_ethqos_driver); if (ret < 0) { ETHQOSINFO("qcom-ethqos: Driver registration failed"); return ret; } ETHQOSINFO("\n"); return ret; } static void __exit qcom_ethqos_exit_module(void) { ETHQOSINFO("\n"); platform_driver_unregister(&qcom_ethqos_driver); ETHQOSINFO("\n"); } /*! * \brief Macro to register the driver registration function. * * \details A module always begin with either the init_module or the function * you specify with module_init call. This is the entry function for modules; * it tells the kernel what functionality the module provides and sets up the * kernel to run the module's functions when they're needed. Once it does this, * entry function returns and the module does nothing until the kernel wants * to do something with the code that the module provides. */ module_init(qcom_ethqos_init_module) /*! * \brief Macro to register the driver un-registration function. * * \details All modules end by calling either cleanup_module or the function * you specify with the module_exit call. This is the exit function for modules; * it undoes whatever entry function did. It unregisters the functionality * that the entry function registered. */ module_exit(qcom_ethqos_exit_module) MODULE_DESCRIPTION("Qualcomm ETHQOS driver"); MODULE_LICENSE("GPL v2");
drivers/net/ethernet/stmicro/stmmac/dwmac-qcom-ethqos.h +49 −1 Original line number Diff line number Diff line Loading @@ -5,12 +5,38 @@ #define _DWMAC_QCOM_ETHQOS_H //#include <linux/msm-bus.h> #include <linux/ipc_logging.h> extern void *ipc_emac_log_ctxt; #define IPCLOG_STATE_PAGES 50 #define __FILENAME__ (strrchr(__FILE__, '/') ? \ strrchr(__FILE__, '/') + 1 : __FILE__) #include <linux/inetdevice.h> #include <linux/inet.h> #include <net/addrconf.h> #include <net/ipv6.h> #include <net/inet_common.h> #include <linux/uaccess.h> #define QCOM_ETH_QOS_MAC_ADDR_LEN 6 #define QCOM_ETH_QOS_MAC_ADDR_STR_LEN 18 #define DRV_NAME "qcom-ethqos" #define ETHQOSDBG(fmt, args...) \ pr_debug(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args) #define ETHQOSERR(fmt, args...) \ pr_err(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args) do {\ pr_err(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args);\ if (ipc_emac_log_ctxt) { \ ipc_log_string(ipc_emac_log_ctxt, \ "%s: %s[%u]:[emac] ERROR:" fmt, __FILENAME__,\ __func__, __LINE__, ## args); \ } \ } while (0) #define ETHQOSINFO(fmt, args...) \ pr_info(DRV_NAME " %s:%d " fmt, __func__, __LINE__, ## args) #define RGMII_IO_MACRO_CONFIG 0x0 Loading Loading @@ -162,6 +188,15 @@ struct qcom_ethqos { int clks_suspended; /* Structure which holds done and wait members */ struct completion clk_enable_done; /* early ethernet parameters */ struct work_struct early_eth; struct delayed_work ipv4_addr_assign_wq; struct delayed_work ipv6_addr_assign_wq; bool early_eth_enabled; /* Key Performance Indicators */ bool print_kpi; struct dentry *debugfs_dir; }; struct pps_cfg { Loading Loading @@ -191,6 +226,19 @@ struct pps_info { int channel_no; }; struct ip_params { unsigned char mac_addr[QCOM_ETH_QOS_MAC_ADDR_LEN]; bool is_valid_mac_addr; char link_speed[32]; bool is_valid_link_speed; char ipv4_addr_str[32]; struct in_addr ipv4_addr; bool is_valid_ipv4_addr; char ipv6_addr_str[48]; struct in6_ifreq ipv6_addr; bool is_valid_ipv6_addr; }; int ethqos_init_reqgulators(struct qcom_ethqos *ethqos); void ethqos_disable_regulators(struct qcom_ethqos *ethqos); int ethqos_init_gpio(struct qcom_ethqos *ethqos); Loading