blob: 214ec156df8a9ad829bcb723de87705e6cd3769e [file] [log] [blame]
/*
**************************************************************************
* Copyright (c) 2016-2018, 2020-2021 The Linux Foundation. All rights reserved.
*
* Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**************************************************************************
*/
#include <linux/delay.h>
#include <linux/ethtool.h>
#include <linux/vmalloc.h>
#include <linux/spinlock.h>
#include <linux/io.h>
#include <nss_dp_dev.h>
#include "qcom_dev.h"
#define QCOM_STAT(m) offsetof(struct nss_dp_hal_gmac_stats, m)
#define QCOM_MIB_STAT(m) offsetof(fal_mib_counter_t, m)
/*
* Ethtool stats pointer structure
*/
struct qcom_ethtool_stats {
uint8_t stat_string[ETH_GSTRING_LEN];
uint32_t stat_offset;
};
/*
* Array of strings describing data plane statistics
*/
static const struct qcom_ethtool_stats qcom_gstrings_stats[] = {
#if defined(NSS_DP_IPQ95XX)
/*
* Per GMAC DMA driver statistics are
* supported today only for IPQ95xx.
*/
{"rx_bytes", QCOM_STAT(rx_bytes)},
{"rx_packets", QCOM_STAT(rx_packets)},
{"rx_dropped", QCOM_STAT(rx_dropped)},
{"rx_fraglist_packets", QCOM_STAT(rx_fraglist_packets)},
{"rx_nr_frag_packets", QCOM_STAT(rx_nr_frag_packets)},
{"rx_nr_frag_headroom_err", QCOM_STAT(rx_nr_frag_headroom_err)},
{"tx_bytes", QCOM_STAT(tx_bytes)},
{"tx_packets", QCOM_STAT(tx_packets)},
{"tx_dropped", QCOM_STAT(tx_dropped)},
{"tx_nr_frag_packets", QCOM_STAT(tx_nr_frag_packets)},
{"tx_fraglist_packets", QCOM_STAT(tx_fraglist_packets)},
{"tx_fraglist_nr_frags_packets", QCOM_STAT(tx_fraglist_with_nr_frags_packets)},
{"tx_tso_packets", QCOM_STAT(tx_tso_packets)},
#endif
};
/*
* Array of strings describing mib statistics
*/
static const struct qcom_ethtool_stats qcom_gstrings_mib_stats[] = {
{"rx_broadcast", QCOM_MIB_STAT(RxBroad)},
{"rx_pause", QCOM_MIB_STAT(RxPause)},
{"rx_unicast", QCOM_MIB_STAT(RxUniCast)},
{"rx_multicast", QCOM_MIB_STAT(RxMulti)},
{"rx_fcserr", QCOM_MIB_STAT(RxFcsErr)},
{"rx_alignerr", QCOM_MIB_STAT(RxAllignErr)},
{"rx_runt", QCOM_MIB_STAT(RxRunt)},
{"rx_frag", QCOM_MIB_STAT(RxFragment)},
{"rx_jmbfcserr", QCOM_MIB_STAT(RxJumboFcsErr)},
{"rx_jmbalignerr", QCOM_MIB_STAT(RxJumboAligenErr)},
{"rx_pkt64", QCOM_MIB_STAT(Rx64Byte)},
{"rx_pkt65to127", QCOM_MIB_STAT(Rx128Byte)},
{"rx_pkt128to255", QCOM_MIB_STAT(Rx256Byte)},
{"rx_pkt256to511", QCOM_MIB_STAT(Rx512Byte)},
{"rx_pkt512to1023", QCOM_MIB_STAT(Rx1024Byte)},
{"rx_pkt1024to1518", QCOM_MIB_STAT(Rx1518Byte)},
{"rx_pkt1519tox", QCOM_MIB_STAT(RxMaxByte)},
{"rx_toolong", QCOM_MIB_STAT(RxTooLong)},
{"rx_pktgoodbyte", QCOM_MIB_STAT(RxGoodByte)},
{"rx_pktbadbyte", QCOM_MIB_STAT(RxBadByte)},
{"rx_overflow", QCOM_MIB_STAT(RxOverFlow)},
{"tx_broadcast", QCOM_MIB_STAT(TxBroad)},
{"tx_pause", QCOM_MIB_STAT(TxPause)},
{"tx_multicast", QCOM_MIB_STAT(TxMulti)},
{"tx_underrun", QCOM_MIB_STAT(TxUnderRun)},
{"tx_pkt64", QCOM_MIB_STAT(Tx64Byte)},
{"tx_pkt65to127", QCOM_MIB_STAT(Tx128Byte)},
{"tx_pkt128to255", QCOM_MIB_STAT(Tx256Byte)},
{"tx_pkt256to511", QCOM_MIB_STAT(Tx512Byte)},
{"tx_pkt512to1023", QCOM_MIB_STAT(Tx1024Byte)},
{"tx_pkt1024to1518", QCOM_MIB_STAT(Tx1518Byte)},
{"tx_pkt1519tox", QCOM_MIB_STAT(TxMaxByte)},
{"tx_oversize", QCOM_MIB_STAT(TxOverSize)},
{"tx_pktbyte_h", QCOM_MIB_STAT(TxByte)},
{"tx_collisions", QCOM_MIB_STAT(TxCollision)},
{"tx_abortcol", QCOM_MIB_STAT(TxAbortCol)},
{"tx_multicol", QCOM_MIB_STAT(TxMultiCol)},
{"tx_singlecol", QCOM_MIB_STAT(TxSingalCol)},
{"tx_exesdeffer", QCOM_MIB_STAT(TxExcDefer)},
{"tx_deffer", QCOM_MIB_STAT(TxDefer)},
{"tx_latecol", QCOM_MIB_STAT(TxLateCol)},
{"tx_unicast", QCOM_MIB_STAT(TxUniCast)},
};
/*
* Array of strings describing private flag names
*/
static const char * const qcom_strings_priv_flags[] = {
"linkpoll",
"tstamp",
"tsmode",
};
#define QCOM_STATS_LEN ARRAY_SIZE(qcom_gstrings_stats)
#define QCOM_MIB_STATS_LEN ARRAY_SIZE(qcom_gstrings_mib_stats)
#define QCOM_PRIV_FLAGS_LEN ARRAY_SIZE(qcom_strings_priv_flags)
/*
* qcom_rx_flow_control()
*/
static void qcom_rx_flow_control(struct nss_gmac_hal_dev *nghd, bool enabled)
{
if (enabled)
qcom_set_rx_flow_ctrl(nghd);
else
qcom_clear_rx_flow_ctrl(nghd);
}
/*
* qcom_tx_flow_control()
*/
static void qcom_tx_flow_control(struct nss_gmac_hal_dev *nghd, bool enabled)
{
if (enabled)
qcom_set_tx_flow_ctrl(nghd);
else
qcom_clear_tx_flow_ctrl(nghd);
}
/*
* qcom_get_mib_stats()
*/
static int32_t qcom_get_mib_stats(struct nss_gmac_hal_dev *nghd)
{
if (qcom_get_stats(nghd))
return -1;
return 0;
}
/*
* qcom_set_maxframe()
*/
static int32_t qcom_set_maxframe(struct nss_gmac_hal_dev *nghd,
uint32_t maxframe)
{
/*
* TODO: In override mode, the NPU configures
* the max frame size into HW, so we do not
* need to do configure the HW here. When we
* need to support changing max frame size for
* host mode DMA driver for IPQ807x/IPQ60xx,
* we would need to call fal_port_max_frame_size_set()
* here by differentiating between override mode and host mode.
*/
return 0;
}
/*
* qcom_get_maxframe()
*/
static int32_t qcom_get_maxframe(struct nss_gmac_hal_dev *nghd)
{
int ret;
uint32_t mtu;
ret = fal_port_max_frame_size_get(0, nghd->mac_id, &mtu);
if (!ret)
return mtu;
return ret;
}
/*
* qcom_get_netdev_stats()
*/
static int32_t qcom_get_netdev_stats(struct nss_gmac_hal_dev *nghd,
struct rtnl_link_stats64 *stats)
{
struct qcom_hal_dev *qhd = (struct qcom_hal_dev *)nghd;
fal_mib_counter_t *hal_stats = &(qhd->stats);
if (qcom_get_mib_stats(nghd))
return -1;
stats->rx_packets = hal_stats->RxUniCast + hal_stats->RxBroad
+ hal_stats->RxMulti;
stats->tx_packets = hal_stats->TxUniCast + hal_stats->TxBroad
+ hal_stats->TxMulti;
stats->rx_bytes = hal_stats->RxGoodByte;
stats->tx_bytes = hal_stats->TxByte;
/* RX errors */
stats->rx_crc_errors = hal_stats->RxFcsErr + hal_stats->RxJumboFcsErr;
stats->rx_frame_errors = hal_stats->RxAllignErr +
hal_stats->RxJumboAligenErr;
stats->rx_fifo_errors = hal_stats->RxRunt;
stats->rx_errors = stats->rx_crc_errors + stats->rx_frame_errors +
stats->rx_fifo_errors;
stats->rx_dropped = hal_stats->RxTooLong + stats->rx_errors;
/* TX errors */
stats->tx_fifo_errors = hal_stats->TxUnderRun;
stats->tx_aborted_errors = hal_stats->TxAbortCol;
stats->tx_errors = stats->tx_fifo_errors + stats->tx_aborted_errors;
stats->collisions = hal_stats->TxCollision;
stats->multicast = hal_stats->RxMulti;
return 0;
}
/*
* qcom_get_strset_count()
* Get string set count for ethtool operations
*/
int32_t qcom_get_strset_count(struct nss_gmac_hal_dev *nghd, int32_t sset)
{
struct net_device *netdev = nghd->netdev;
switch (sset) {
case ETH_SS_STATS:
return (QCOM_STATS_LEN + QCOM_MIB_STATS_LEN);
case ETH_SS_PRIV_FLAGS:
return QCOM_PRIV_FLAGS_LEN;
}
netdev_dbg(netdev, "%s: Invalid string set\n", __func__);
return -EPERM;
}
/*
* qcom_get_strings()
* Get strings
*/
int32_t qcom_get_strings(struct nss_gmac_hal_dev *nghd, int32_t sset,
uint8_t *data)
{
struct net_device *netdev = nghd->netdev;
int i;
switch (sset) {
case ETH_SS_STATS:
for (i = 0; i < QCOM_STATS_LEN; i++) {
memcpy(data, qcom_gstrings_stats[i].stat_string,
strlen(qcom_gstrings_stats[i].stat_string));
data += ETH_GSTRING_LEN;
}
for (i = 0; i < QCOM_MIB_STATS_LEN; i++) {
memcpy(data, qcom_gstrings_mib_stats[i].stat_string,
strlen(qcom_gstrings_mib_stats[i].stat_string));
data += ETH_GSTRING_LEN;
}
break;
case ETH_SS_PRIV_FLAGS:
for (i = 0; i < QCOM_PRIV_FLAGS_LEN; i++) {
memcpy(data, qcom_strings_priv_flags[i],
strlen(qcom_strings_priv_flags[i]));
data += ETH_GSTRING_LEN;
}
break;
default:
netdev_dbg(netdev, "%s: Invalid string set\n", __func__);
return -EPERM;
}
return 0;
}
/*
* qcom_get_eth_stats()
*/
static int32_t qcom_get_eth_stats(struct nss_gmac_hal_dev *nghd, uint64_t *data, struct nss_dp_gmac_stats *stats)
{
struct qcom_hal_dev *qhd = (struct qcom_hal_dev *)nghd;
fal_mib_counter_t *mib_stats = &(qhd->stats);
uint8_t *p;
int i, i_mib;
/*
* Populate data plane statistics.
*/
for (i = 0; i < QCOM_STATS_LEN; i++) {
p = ((uint8_t *)(stats)
+ qcom_gstrings_stats[i].stat_offset);
data[i] = *(uint64_t *)p;
}
/*
* Get MIB statistics
*/
if (qcom_get_mib_stats(nghd)) {
return -1;
}
/*
* Populate MIB statistics
*/
for (i_mib = 0; i_mib < QCOM_MIB_STATS_LEN; i_mib++) {
p = (uint8_t *)mib_stats
+ qcom_gstrings_mib_stats[i_mib].stat_offset;
i = QCOM_STATS_LEN + i_mib;
data[i] = *(uint32_t *)p;
}
return 0;
}
/*
* qcom_send_pause_frame()
*/
static void qcom_send_pause_frame(struct nss_gmac_hal_dev *nghd)
{
qcom_set_ctrl2_test_pause(nghd);
}
/*
* qcom_stop_pause_frame()
*/
static void qcom_stop_pause_frame(struct nss_gmac_hal_dev *nghd)
{
qcom_reset_ctrl2_test_pause(nghd);
}
/*
* qcom_start()
*/
static int32_t qcom_start(struct nss_gmac_hal_dev *nghd)
{
qcom_set_full_duplex(nghd);
qcom_tx_enable(nghd);
qcom_rx_enable(nghd);
netdev_dbg(nghd->netdev, "%s: mac_base:0x%px mac_enable:0x%x\n",
__func__, nghd->mac_base,
hal_read_relaxed_reg(nghd->mac_base, QCOM_MAC_ENABLE));
return 0;
}
/*
* qcom_stop()
*/
static int32_t qcom_stop(struct nss_gmac_hal_dev *nghd)
{
qcom_tx_disable(nghd);
qcom_rx_disable(nghd);
netdev_dbg(nghd->netdev, "%s: mac_base:0x%px mac_enable:0x%x\n",
__func__, nghd->mac_base,
hal_read_relaxed_reg(nghd->mac_base, QCOM_MAC_ENABLE));
return 0;
}
/*
* qcom_init()
*/
static void *qcom_init(struct nss_gmac_hal_platform_data *gmacpdata)
{
struct qcom_hal_dev *qhd = NULL;
struct net_device *ndev = NULL;
struct nss_dp_dev *dp_priv = NULL;
struct resource *res;
ndev = gmacpdata->netdev;
dp_priv = netdev_priv(ndev);
res = platform_get_resource(dp_priv->pdev, IORESOURCE_MEM, 0);
if (!res) {
netdev_dbg(ndev, "Resource get failed.\n");
return NULL;
}
qhd = (struct qcom_hal_dev *)devm_kzalloc(&dp_priv->pdev->dev,
sizeof(struct qcom_hal_dev), GFP_KERNEL);
if (!qhd) {
netdev_dbg(ndev, "kzalloc failed. Returning...\n");
return NULL;
}
qhd->nghd.mac_reg_len = resource_size(res);
qhd->nghd.memres = devm_request_mem_region(&dp_priv->pdev->dev,
res->start,
resource_size(res),
ndev->name);
if (!qhd->nghd.memres) {
netdev_dbg(ndev, "Request mem region failed. Returning.\n");
devm_kfree(&dp_priv->pdev->dev, qhd);
return NULL;
}
/* Save netdev context in QCOM HAL context */
qhd->nghd.netdev = gmacpdata->netdev;
qhd->nghd.mac_id = gmacpdata->macid;
/* Populate the mac base addresses */
qhd->nghd.mac_base = devm_ioremap_nocache(&dp_priv->pdev->dev,
res->start, resource_size(res));
if (!qhd->nghd.mac_base) {
netdev_dbg(ndev, "ioremap fail.\n");
devm_release_mem_region(&dp_priv->pdev->dev,
qhd->nghd.memres->start,
qhd->nghd.mac_reg_len);
devm_kfree(&dp_priv->pdev->dev, qhd);
return NULL;
}
spin_lock_init(&qhd->nghd.slock);
netdev_dbg(ndev, "ioremap OK.Size 0x%x Ndev base 0x%lx macbase 0x%px\n",
gmacpdata->reg_len,
ndev->base_addr,
qhd->nghd.mac_base);
/* Reset MIB Stats */
if (fal_mib_port_flush_counters(0, qhd->nghd.mac_id)) {
netdev_dbg(ndev, "MIB stats Reset fail.\n");
}
return (struct nss_gmac_hal_dev *)qhd;
}
/*
* qcom_get_mac_address()
*/
static void qcom_get_mac_address(struct nss_gmac_hal_dev *nghd,
uint8_t *macaddr)
{
uint32_t data = hal_read_relaxed_reg(nghd->mac_base, QCOM_MAC_ADDR0);
macaddr[5] = (data >> 8) & 0xff;
macaddr[4] = (data) & 0xff;
data = hal_read_relaxed_reg(nghd->mac_base, QCOM_MAC_ADDR1);
macaddr[0] = (data >> 24) & 0xff;
macaddr[1] = (data >> 16) & 0xff;
macaddr[2] = (data >> 8) & 0xff;
macaddr[3] = (data) & 0xff;
}
/*
* qcom_set_mac_address()
*/
static void qcom_set_mac_address(struct nss_gmac_hal_dev *nghd,
uint8_t *macaddr)
{
uint32_t data = (macaddr[5] << 8) | macaddr[4];
hal_write_relaxed_reg(nghd->mac_base, QCOM_MAC_ADDR0, data);
data = (macaddr[0] << 24) | (macaddr[1] << 16)
| (macaddr[2] << 8) | macaddr[3];
hal_write_relaxed_reg(nghd->mac_base, QCOM_MAC_ADDR1, data);
}
/*
* qcom_exit()
*/
static void qcom_exit(struct nss_gmac_hal_dev *nghd)
{
struct nss_dp_dev *dp_priv = NULL;
struct qcom_hal_dev *qhd = (struct qcom_hal_dev *)nghd;
netdev_dbg(nghd->netdev, "Freeing up dev memory.\n");
dp_priv = netdev_priv(nghd->netdev);
devm_iounmap(&dp_priv->pdev->dev,
(void *)nghd->mac_base);
devm_release_mem_region(&dp_priv->pdev->dev,
(nghd->memres)->start,
nghd->mac_reg_len);
nghd->memres = NULL;
nghd->mac_base = NULL;
devm_kfree(&dp_priv->pdev->dev, qhd);
}
/*
* MAC hal_ops base structure
*/
struct nss_gmac_hal_ops qcom_gmac_ops = {
.init = &qcom_init,
.start = &qcom_start,
.stop = &qcom_stop,
.exit = &qcom_exit,
.setmacaddr = &qcom_set_mac_address,
.getmacaddr = &qcom_get_mac_address,
.rxflowcontrol = &qcom_rx_flow_control,
.txflowcontrol = &qcom_tx_flow_control,
.getstats = &qcom_get_mib_stats,
.setmaxframe = &qcom_set_maxframe,
.getmaxframe = &qcom_get_maxframe,
.getndostats = &qcom_get_netdev_stats,
.getssetcount = &qcom_get_strset_count,
.getstrings = &qcom_get_strings,
.getethtoolstats = &qcom_get_eth_stats,
.sendpause = &qcom_send_pause_frame,
.stoppause = &qcom_stop_pause_frame,
};