2019-04-27 01:12:23 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2012-03-19 04:17:54 +08:00
|
|
|
/*
|
|
|
|
* Texas Instruments Ethernet Switch Driver
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012 Texas Instruments
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/timer.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <linux/irqreturn.h>
|
|
|
|
#include <linux/interrupt.h>
|
|
|
|
#include <linux/if_ether.h>
|
|
|
|
#include <linux/etherdevice.h>
|
|
|
|
#include <linux/netdevice.h>
|
2012-10-29 16:45:20 +08:00
|
|
|
#include <linux/net_tstamp.h>
|
2012-03-19 04:17:54 +08:00
|
|
|
#include <linux/phy.h>
|
2018-11-26 08:15:25 +08:00
|
|
|
#include <linux/phy/phy.h>
|
2012-03-19 04:17:54 +08:00
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/delay.h>
|
2012-07-17 16:09:50 +08:00
|
|
|
#include <linux/pm_runtime.h>
|
2018-05-31 05:51:54 +08:00
|
|
|
#include <linux/gpio/consumer.h>
|
2012-07-30 18:17:14 +08:00
|
|
|
#include <linux/of.h>
|
2015-10-17 12:04:35 +08:00
|
|
|
#include <linux/of_mdio.h>
|
2012-07-30 18:17:14 +08:00
|
|
|
#include <linux/of_net.h>
|
|
|
|
#include <linux/of_device.h>
|
2013-02-05 16:26:48 +08:00
|
|
|
#include <linux/if_vlan.h>
|
2018-04-06 07:25:34 +08:00
|
|
|
#include <linux/kmemleak.h>
|
2018-05-17 06:21:45 +08:00
|
|
|
#include <linux/sys_soc.h>
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2013-06-07 02:15:14 +08:00
|
|
|
#include <linux/pinctrl/consumer.h>
|
2018-07-24 05:26:31 +08:00
|
|
|
#include <net/pkt_cls.h>
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2013-08-19 20:17:40 +08:00
|
|
|
#include "cpsw.h"
|
2012-03-19 04:17:54 +08:00
|
|
|
#include "cpsw_ale.h"
|
2019-04-27 01:12:37 +08:00
|
|
|
#include "cpsw_priv.h"
|
2019-04-27 01:12:41 +08:00
|
|
|
#include "cpsw_sl.h"
|
2012-10-29 16:45:20 +08:00
|
|
|
#include "cpts.h"
|
2012-03-19 04:17:54 +08:00
|
|
|
#include "davinci_cpdma.h"
|
|
|
|
|
net: ethernet: ti: cpsw: add CBS Qdisc offload
The cpsw has up to 4 FIFOs per port and upper 3 FIFOs can feed rate
limited queue with shaping. In order to set and enable shaping for
those 3 FIFOs queues the network device with CBS qdisc attached is
needed. The CBS configuration is added for dual-emac/single port mode
only, but potentially can be used in switch mode also, based on
switchdev for instance.
Despite the FIFO shapers can work w/o cpdma level shapers the base
usage must be in combine with cpdma level shapers as described in TRM,
that are set as maximum rates for interface queues with sysfs.
One of the possible configuration with txq shapers and CBS shapers:
Configured with echo RATE >
/sys/class/net/eth0/queues/tx-0/tx_maxrate
/---------------------------------------------------
/
/ cpdma level shapers
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| c7 | | c6 | | c5 | | c4 | | c3 | | c2 | | c1 | | c0 |
\ / \ / \ / \ / \ / \ / \ / \ /
\ / \ / \ / \ / \ / \ / \ / \ /
\/ \/ \/ \/ \/ \/ \/ \/
+---------|------|------|------|-------------------------------------+
| +----+ | | +---+ |
| | +----+ | | |
| v v v v |
| +----+ +----+ +----+ +----+ p p+----+ +----+ +----+ +----+ |
| | | | | | | | | o o| | | | | | | | |
| | f3 | | f2 | | f1 | | f0 | r CPSW r| f3 | | f2 | | f1 | | f0 | |
| | | | | | | | | t t| | | | | | | | |
| \ / \ / \ / \ / 0 1\ / \ / \ / \ / |
| \ X \ / \ / \ / \ / \ / \ / \ / |
| \/ \ \/ \/ \/ \/ \/ \/ \/ |
+-------\------------------------------------------------------------+
\
\ FIFO shaper, set with CBS offload added in this patch,
\ FIFO0 cannot be rate limited
------------------------------------------------------
CBS shaper configuration is supposed to be used with root MQPRIO Qdisc
offload allowing to add sk_prio->tc->txq maps that direct traffic to
appropriate tx queue and maps L2 priority to FIFO shaper.
The CBS shaper is intended to be used for AVB where L2 priority
(pcp field) is used to differentiate class of traffic. So additionally
vlan needs to be created with appropriate egress sk_prio->l2 prio map.
If CBS has several tx queues assigned to it, the sum of their
bandwidth has not overlap bandwidth set for CBS. It's recomended the
CBS bandwidth to be a little bit more.
The CBS shaper is configured with CBS qdisc offload interface using tc
tool from iproute2 packet.
For instance:
$ tc qdisc replace dev eth0 handle 100: parent root mqprio num_tc 3 \
map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 1
$ tc -g class show dev eth0
+---(100:ffe2) mqprio
| +---(100:3) mqprio
| +---(100:4) mqprio
|
+---(100:ffe1) mqprio
| +---(100:2) mqprio
|
+---(100:ffe0) mqprio
+---(100:1) mqprio
$ tc qdisc add dev eth0 parent 100:1 cbs locredit -1440 \
hicredit 60 sendslope -960000 idleslope 40000 offload 1
$ tc qdisc add dev eth0 parent 100:2 cbs locredit -1470 \
hicredit 62 sendslope -980000 idleslope 20000 offload 1
The above code set CBS shapers for tc0 and tc1, for that txq0 and
txq1 is used. Pay attention, the real set bandwidth can differ a bit
due to discreteness of configuration parameters.
Here parameters like locredit, hicredit and sendslope are ignored
internally and are supposed to be set with assumption that maximum
frame size for frame - 1500.
It's supposed that interface speed is not changed while reconnection,
not always is true, so inform user in case speed of interface was
changed, as it can impact on dependent shapers configuration.
For more examples see Documentation.
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-07-24 05:26:32 +08:00
|
|
|
#include <net/pkt_sched.h>
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
static int debug_level;
|
|
|
|
module_param(debug_level, int, 0);
|
|
|
|
MODULE_PARM_DESC(debug_level, "cpsw debug level (NETIF_MSG bits)");
|
|
|
|
|
|
|
|
static int ale_ageout = 10;
|
|
|
|
module_param(ale_ageout, int, 0);
|
|
|
|
MODULE_PARM_DESC(ale_ageout, "cpsw ale ageout interval (seconds)");
|
|
|
|
|
|
|
|
static int rx_packet_max = CPSW_MAX_PACKET_SIZE;
|
|
|
|
module_param(rx_packet_max, int, 0);
|
|
|
|
MODULE_PARM_DESC(rx_packet_max, "maximum receive packet size (bytes)");
|
|
|
|
|
2017-01-07 04:07:33 +08:00
|
|
|
static int descs_pool_size = CPSW_CPDMA_DESCS_POOL_SIZE_DEFAULT;
|
|
|
|
module_param(descs_pool_size, int, 0444);
|
|
|
|
MODULE_PARM_DESC(descs_pool_size, "Number of CPDMA CPPI descriptors in pool");
|
|
|
|
|
2013-02-11 17:52:20 +08:00
|
|
|
#define for_each_slave(priv, func, arg...) \
|
|
|
|
do { \
|
2013-04-24 16:48:24 +08:00
|
|
|
struct cpsw_slave *slave; \
|
2016-08-10 07:22:42 +08:00
|
|
|
struct cpsw_common *cpsw = (priv)->cpsw; \
|
2013-04-24 16:48:24 +08:00
|
|
|
int n; \
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac) \
|
|
|
|
(func)((cpsw)->slaves + priv->emac_port, ##arg);\
|
2013-02-11 17:52:20 +08:00
|
|
|
else \
|
2016-08-10 07:22:42 +08:00
|
|
|
for (n = cpsw->data.slaves, \
|
|
|
|
slave = cpsw->slaves; \
|
2013-04-24 16:48:24 +08:00
|
|
|
n; n--) \
|
|
|
|
(func)(slave++, ##arg); \
|
2013-02-11 17:52:20 +08:00
|
|
|
} while (0)
|
|
|
|
|
2018-11-09 04:27:57 +08:00
|
|
|
static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev,
|
|
|
|
__be16 proto, u16 vid);
|
|
|
|
|
2014-01-23 02:33:12 +08:00
|
|
|
static void cpsw_set_promiscious(struct net_device *ndev, bool enable)
|
|
|
|
{
|
2016-08-10 07:22:44 +08:00
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
|
|
|
struct cpsw_ale *ale = cpsw->ale;
|
2014-01-23 02:33:12 +08:00
|
|
|
int i;
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac) {
|
2014-01-23 02:33:12 +08:00
|
|
|
bool flag = false;
|
|
|
|
|
|
|
|
/* Enabling promiscuous mode for one interface will be
|
|
|
|
* common for both the interface as the interface shares
|
|
|
|
* the same hardware resource.
|
|
|
|
*/
|
2016-08-10 07:22:42 +08:00
|
|
|
for (i = 0; i < cpsw->data.slaves; i++)
|
|
|
|
if (cpsw->slaves[i].ndev->flags & IFF_PROMISC)
|
2014-01-23 02:33:12 +08:00
|
|
|
flag = true;
|
|
|
|
|
|
|
|
if (!enable && flag) {
|
|
|
|
enable = true;
|
|
|
|
dev_err(&ndev->dev, "promiscuity not disabled as the other interface is still in promiscuity mode\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (enable) {
|
|
|
|
/* Enable Bypass */
|
|
|
|
cpsw_ale_control_set(ale, 0, ALE_BYPASS, 1);
|
|
|
|
|
|
|
|
dev_dbg(&ndev->dev, "promiscuity enabled\n");
|
|
|
|
} else {
|
|
|
|
/* Disable Bypass */
|
|
|
|
cpsw_ale_control_set(ale, 0, ALE_BYPASS, 0);
|
|
|
|
dev_dbg(&ndev->dev, "promiscuity disabled\n");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (enable) {
|
|
|
|
unsigned long timeout = jiffies + HZ;
|
|
|
|
|
2014-11-01 01:28:54 +08:00
|
|
|
/* Disable Learn for all ports (host is port 0 and slaves are port 1 and up */
|
2016-08-10 07:22:42 +08:00
|
|
|
for (i = 0; i <= cpsw->data.slaves; i++) {
|
2014-01-23 02:33:12 +08:00
|
|
|
cpsw_ale_control_set(ale, i,
|
|
|
|
ALE_PORT_NOLEARN, 1);
|
|
|
|
cpsw_ale_control_set(ale, i,
|
|
|
|
ALE_PORT_NO_SA_UPDATE, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Clear All Untouched entries */
|
|
|
|
cpsw_ale_control_set(ale, 0, ALE_AGEOUT, 1);
|
|
|
|
do {
|
|
|
|
cpu_relax();
|
|
|
|
if (cpsw_ale_control_get(ale, 0, ALE_AGEOUT))
|
|
|
|
break;
|
|
|
|
} while (time_after(timeout, jiffies));
|
|
|
|
cpsw_ale_control_set(ale, 0, ALE_AGEOUT, 1);
|
|
|
|
|
|
|
|
/* Clear all mcast from ALE */
|
drivers: net: cpsw: fix port_mask parameters in ale calls
ALE APIs expect to receive port masks as input values for arguments
port_mask, untag, reg_mcast, unreg_mcast. But there are few places in
code where port masks are passed left-shifted by cpsw_priv->host_port,
like below:
cpsw_ale_add_vlan(priv->ale, priv->data.default_vlan,
ALE_ALL_PORTS << priv->host_port,
ALE_ALL_PORTS << priv->host_port, 0, 0);
and cpsw is still working just because priv->host_port == 0
and has never ever been changed.
Hence, fix port_mask parameters in ALE APIs calls and drop
"<< priv->host_port" from all places where it's used to
shift valid port mask.
Signed-off-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-04-07 20:16:43 +08:00
|
|
|
cpsw_ale_flush_multicast(ale, ALE_ALL_PORTS, -1);
|
2018-11-09 04:27:56 +08:00
|
|
|
__hw_addr_ref_unsync_dev(&ndev->mc, ndev, NULL);
|
2014-01-23 02:33:12 +08:00
|
|
|
|
|
|
|
/* Flood All Unicast Packets to Host port */
|
|
|
|
cpsw_ale_control_set(ale, 0, ALE_P0_UNI_FLOOD, 1);
|
|
|
|
dev_dbg(&ndev->dev, "promiscuity enabled\n");
|
|
|
|
} else {
|
2014-11-01 01:28:54 +08:00
|
|
|
/* Don't Flood All Unicast Packets to Host port */
|
2014-01-23 02:33:12 +08:00
|
|
|
cpsw_ale_control_set(ale, 0, ALE_P0_UNI_FLOOD, 0);
|
|
|
|
|
2014-11-01 01:28:54 +08:00
|
|
|
/* Enable Learn for all ports (host is port 0 and slaves are port 1 and up */
|
2016-08-10 07:22:42 +08:00
|
|
|
for (i = 0; i <= cpsw->data.slaves; i++) {
|
2014-01-23 02:33:12 +08:00
|
|
|
cpsw_ale_control_set(ale, i,
|
|
|
|
ALE_PORT_NOLEARN, 0);
|
|
|
|
cpsw_ale_control_set(ale, i,
|
|
|
|
ALE_PORT_NO_SA_UPDATE, 0);
|
|
|
|
}
|
|
|
|
dev_dbg(&ndev->dev, "promiscuity disabled\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-09 04:27:56 +08:00
|
|
|
/**
|
|
|
|
* cpsw_set_mc - adds multicast entry to the table if it's not added or deletes
|
|
|
|
* if it's not deleted
|
|
|
|
* @ndev: device to sync
|
|
|
|
* @addr: address to be added or deleted
|
|
|
|
* @vid: vlan id, if vid < 0 set/unset address for real device
|
|
|
|
* @add: add address if the flag is set or remove otherwise
|
|
|
|
*/
|
|
|
|
static int cpsw_set_mc(struct net_device *ndev, const u8 *addr,
|
|
|
|
int vid, int add)
|
2018-10-12 23:28:15 +08:00
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
2018-11-09 04:27:56 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
int mask, flags, ret;
|
|
|
|
|
|
|
|
if (vid < 0) {
|
|
|
|
if (cpsw->data.dual_emac)
|
|
|
|
vid = cpsw->slaves[priv->emac_port].port_vlan;
|
|
|
|
else
|
|
|
|
vid = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
mask = cpsw->data.dual_emac ? ALE_PORT_HOST : ALE_ALL_PORTS;
|
|
|
|
flags = vid ? ALE_VLAN : 0;
|
|
|
|
|
|
|
|
if (add)
|
|
|
|
ret = cpsw_ale_add_mcast(cpsw->ale, addr, mask, flags, vid, 0);
|
|
|
|
else
|
|
|
|
ret = cpsw_ale_del_mcast(cpsw->ale, addr, 0, flags, vid);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_update_vlan_mc(struct net_device *vdev, int vid, void *ctx)
|
|
|
|
{
|
|
|
|
struct addr_sync_ctx *sync_ctx = ctx;
|
|
|
|
struct netdev_hw_addr *ha;
|
|
|
|
int found = 0, ret = 0;
|
|
|
|
|
|
|
|
if (!vdev || !(vdev->flags & IFF_UP))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* vlan address is relevant if its sync_cnt != 0 */
|
|
|
|
netdev_for_each_mc_addr(ha, vdev) {
|
|
|
|
if (ether_addr_equal(ha->addr, sync_ctx->addr)) {
|
|
|
|
found = ha->sync_cnt;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (found)
|
|
|
|
sync_ctx->consumed++;
|
|
|
|
|
|
|
|
if (sync_ctx->flush) {
|
|
|
|
if (!found)
|
|
|
|
cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (found)
|
|
|
|
ret = cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 1);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_add_mc_addr(struct net_device *ndev, const u8 *addr, int num)
|
|
|
|
{
|
|
|
|
struct addr_sync_ctx sync_ctx;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
sync_ctx.consumed = 0;
|
|
|
|
sync_ctx.addr = addr;
|
|
|
|
sync_ctx.ndev = ndev;
|
|
|
|
sync_ctx.flush = 0;
|
|
|
|
|
|
|
|
ret = vlan_for_each(ndev, cpsw_update_vlan_mc, &sync_ctx);
|
|
|
|
if (sync_ctx.consumed < num && !ret)
|
|
|
|
ret = cpsw_set_mc(ndev, addr, -1, 1);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_del_mc_addr(struct net_device *ndev, const u8 *addr, int num)
|
|
|
|
{
|
|
|
|
struct addr_sync_ctx sync_ctx;
|
|
|
|
|
|
|
|
sync_ctx.consumed = 0;
|
|
|
|
sync_ctx.addr = addr;
|
|
|
|
sync_ctx.ndev = ndev;
|
|
|
|
sync_ctx.flush = 1;
|
|
|
|
|
|
|
|
vlan_for_each(ndev, cpsw_update_vlan_mc, &sync_ctx);
|
|
|
|
if (sync_ctx.consumed == num)
|
|
|
|
cpsw_set_mc(ndev, addr, -1, 0);
|
2018-10-12 23:28:15 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-11-09 04:27:56 +08:00
|
|
|
static int cpsw_purge_vlan_mc(struct net_device *vdev, int vid, void *ctx)
|
2012-10-29 16:45:11 +08:00
|
|
|
{
|
2018-11-09 04:27:56 +08:00
|
|
|
struct addr_sync_ctx *sync_ctx = ctx;
|
|
|
|
struct netdev_hw_addr *ha;
|
|
|
|
int found = 0;
|
2015-01-13 20:05:49 +08:00
|
|
|
|
2018-11-09 04:27:56 +08:00
|
|
|
if (!vdev || !(vdev->flags & IFF_UP))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* vlan address is relevant if its sync_cnt != 0 */
|
|
|
|
netdev_for_each_mc_addr(ha, vdev) {
|
|
|
|
if (ether_addr_equal(ha->addr, sync_ctx->addr)) {
|
|
|
|
found = ha->sync_cnt;
|
|
|
|
break;
|
|
|
|
}
|
2018-10-12 23:28:15 +08:00
|
|
|
}
|
|
|
|
|
2018-11-09 04:27:56 +08:00
|
|
|
if (!found)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
sync_ctx->consumed++;
|
|
|
|
cpsw_set_mc(sync_ctx->ndev, sync_ctx->addr, vid, 0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_purge_all_mc(struct net_device *ndev, const u8 *addr, int num)
|
|
|
|
{
|
|
|
|
struct addr_sync_ctx sync_ctx;
|
|
|
|
|
|
|
|
sync_ctx.addr = addr;
|
|
|
|
sync_ctx.ndev = ndev;
|
|
|
|
sync_ctx.consumed = 0;
|
|
|
|
|
|
|
|
vlan_for_each(ndev, cpsw_purge_vlan_mc, &sync_ctx);
|
|
|
|
if (sync_ctx.consumed < num)
|
|
|
|
cpsw_set_mc(ndev, addr, -1, 0);
|
|
|
|
|
2018-10-12 23:28:15 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cpsw_ndo_set_rx_mode(struct net_device *ndev)
|
|
|
|
{
|
2019-04-27 01:12:33 +08:00
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
int slave_port = -1;
|
|
|
|
|
|
|
|
if (cpsw->data.dual_emac)
|
|
|
|
slave_port = priv->emac_port + 1;
|
2012-10-29 16:45:11 +08:00
|
|
|
|
|
|
|
if (ndev->flags & IFF_PROMISC) {
|
|
|
|
/* Enable promiscuous mode */
|
2014-01-23 02:33:12 +08:00
|
|
|
cpsw_set_promiscious(ndev, true);
|
2019-04-27 01:12:33 +08:00
|
|
|
cpsw_ale_set_allmulti(cpsw->ale, IFF_ALLMULTI, slave_port);
|
2012-10-29 16:45:11 +08:00
|
|
|
return;
|
2014-01-23 02:33:12 +08:00
|
|
|
} else {
|
|
|
|
/* Disable promiscuous mode */
|
|
|
|
cpsw_set_promiscious(ndev, false);
|
2012-10-29 16:45:11 +08:00
|
|
|
}
|
|
|
|
|
2014-11-01 01:38:52 +08:00
|
|
|
/* Restore allmulti on vlans if necessary */
|
2019-04-27 01:12:33 +08:00
|
|
|
cpsw_ale_set_allmulti(cpsw->ale,
|
|
|
|
ndev->flags & IFF_ALLMULTI, slave_port);
|
2012-10-29 16:45:11 +08:00
|
|
|
|
2018-11-09 04:27:56 +08:00
|
|
|
/* add/remove mcast address either for real netdev or for vlan */
|
|
|
|
__hw_addr_ref_sync_dev(&ndev->mc, ndev, cpsw_add_mc_addr,
|
|
|
|
cpsw_del_mc_addr);
|
2012-10-29 16:45:11 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:42 +08:00
|
|
|
void cpsw_intr_enable(struct cpsw_common *cpsw)
|
2012-03-19 04:17:54 +08:00
|
|
|
{
|
2017-12-01 08:21:11 +08:00
|
|
|
writel_relaxed(0xFF, &cpsw->wr_regs->tx_en);
|
|
|
|
writel_relaxed(0xFF, &cpsw->wr_regs->rx_en);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-08-10 07:22:40 +08:00
|
|
|
cpdma_ctlr_int_ctrl(cpsw->dma, true);
|
2012-03-19 04:17:54 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:42 +08:00
|
|
|
void cpsw_intr_disable(struct cpsw_common *cpsw)
|
2012-03-19 04:17:54 +08:00
|
|
|
{
|
2017-12-01 08:21:11 +08:00
|
|
|
writel_relaxed(0, &cpsw->wr_regs->tx_en);
|
|
|
|
writel_relaxed(0, &cpsw->wr_regs->rx_en);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-08-10 07:22:40 +08:00
|
|
|
cpdma_ctlr_int_ctrl(cpsw->dma, false);
|
2012-03-19 04:17:54 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:42 +08:00
|
|
|
void cpsw_tx_handler(void *token, int len, int status)
|
2012-03-19 04:17:54 +08:00
|
|
|
{
|
2016-08-23 02:18:26 +08:00
|
|
|
struct netdev_queue *txq;
|
2012-03-19 04:17:54 +08:00
|
|
|
struct sk_buff *skb = token;
|
|
|
|
struct net_device *ndev = skb->dev;
|
2016-08-10 07:22:44 +08:00
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2013-01-17 14:31:34 +08:00
|
|
|
/* Check whether the queue is stopped due to stalled tx dma, if the
|
|
|
|
* queue is stopped then start the queue as we have free desc for tx
|
|
|
|
*/
|
2016-08-23 02:18:26 +08:00
|
|
|
txq = netdev_get_tx_queue(ndev, skb_get_queue_mapping(skb));
|
|
|
|
if (unlikely(netif_tx_queue_stopped(txq)))
|
|
|
|
netif_tx_wake_queue(txq);
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
cpts_tx_timestamp(cpsw->cpts, skb);
|
2014-03-10 20:12:23 +08:00
|
|
|
ndev->stats.tx_packets++;
|
|
|
|
ndev->stats.tx_bytes += len;
|
2012-03-19 04:17:54 +08:00
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
}
|
|
|
|
|
2018-03-16 04:15:50 +08:00
|
|
|
static void cpsw_rx_vlan_encap(struct sk_buff *skb)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(skb->dev);
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
u32 rx_vlan_encap_hdr = *((u32 *)skb->data);
|
|
|
|
u16 vtag, vid, prio, pkt_type;
|
|
|
|
|
|
|
|
/* Remove VLAN header encapsulation word */
|
|
|
|
skb_pull(skb, CPSW_RX_VLAN_ENCAP_HDR_SIZE);
|
|
|
|
|
|
|
|
pkt_type = (rx_vlan_encap_hdr >>
|
|
|
|
CPSW_RX_VLAN_ENCAP_HDR_PKT_TYPE_SHIFT) &
|
|
|
|
CPSW_RX_VLAN_ENCAP_HDR_PKT_TYPE_MSK;
|
|
|
|
/* Ignore unknown & Priority-tagged packets*/
|
|
|
|
if (pkt_type == CPSW_RX_VLAN_ENCAP_HDR_PKT_RESERV ||
|
|
|
|
pkt_type == CPSW_RX_VLAN_ENCAP_HDR_PKT_PRIO_TAG)
|
|
|
|
return;
|
|
|
|
|
|
|
|
vid = (rx_vlan_encap_hdr >>
|
|
|
|
CPSW_RX_VLAN_ENCAP_HDR_VID_SHIFT) &
|
|
|
|
VLAN_VID_MASK;
|
|
|
|
/* Ignore vid 0 and pass packet as is */
|
|
|
|
if (!vid)
|
|
|
|
return;
|
|
|
|
/* Ignore default vlans in dual mac mode */
|
|
|
|
if (cpsw->data.dual_emac &&
|
|
|
|
vid == cpsw->slaves[priv->emac_port].port_vlan)
|
|
|
|
return;
|
|
|
|
|
|
|
|
prio = (rx_vlan_encap_hdr >>
|
|
|
|
CPSW_RX_VLAN_ENCAP_HDR_PRIO_SHIFT) &
|
|
|
|
CPSW_RX_VLAN_ENCAP_HDR_PRIO_MSK;
|
|
|
|
|
|
|
|
vtag = (prio << VLAN_PRIO_SHIFT) | vid;
|
|
|
|
__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vtag);
|
|
|
|
|
|
|
|
/* strip vlan tag for VLAN-tagged packet */
|
|
|
|
if (pkt_type == CPSW_RX_VLAN_ENCAP_HDR_PKT_VLAN_TAG) {
|
|
|
|
memmove(skb->data + VLAN_HLEN, skb->data, 2 * ETH_ALEN);
|
|
|
|
skb_pull(skb, VLAN_HLEN);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-12 07:58:07 +08:00
|
|
|
static void cpsw_rx_handler(void *token, int len, int status)
|
2012-03-19 04:17:54 +08:00
|
|
|
{
|
2016-08-23 02:18:26 +08:00
|
|
|
struct cpdma_chan *ch;
|
2012-03-19 04:17:54 +08:00
|
|
|
struct sk_buff *skb = token;
|
2013-04-23 15:31:39 +08:00
|
|
|
struct sk_buff *new_skb;
|
2012-03-19 04:17:54 +08:00
|
|
|
struct net_device *ndev = skb->dev;
|
2018-07-31 06:05:39 +08:00
|
|
|
int ret = 0, port;
|
2016-08-10 07:22:44 +08:00
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
2018-11-12 22:00:22 +08:00
|
|
|
struct cpsw_priv *priv;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2018-07-31 06:05:39 +08:00
|
|
|
if (cpsw->data.dual_emac) {
|
|
|
|
port = CPDMA_RX_SOURCE_PORT(status);
|
|
|
|
if (port) {
|
|
|
|
ndev = cpsw->slaves[--port].ndev;
|
|
|
|
skb->dev = ndev;
|
|
|
|
}
|
|
|
|
}
|
2013-02-11 17:52:20 +08:00
|
|
|
|
2014-04-10 16:53:23 +08:00
|
|
|
if (unlikely(status < 0) || unlikely(!netif_running(ndev))) {
|
2017-01-20 00:58:26 +08:00
|
|
|
/* In dual emac mode check for all interfaces */
|
2017-02-14 22:02:36 +08:00
|
|
|
if (cpsw->data.dual_emac && cpsw->usage_count &&
|
2017-01-20 00:58:26 +08:00
|
|
|
(status >= 0)) {
|
2014-09-10 19:08:09 +08:00
|
|
|
/* The packet received is for the interface which
|
|
|
|
* is already down and the other interface is up
|
2015-03-07 12:49:12 +08:00
|
|
|
* and running, instead of freeing which results
|
2014-09-10 19:08:09 +08:00
|
|
|
* in reducing of the number of rx descriptor in
|
|
|
|
* DMA engine, requeue skb back to cpdma.
|
|
|
|
*/
|
|
|
|
new_skb = skb;
|
|
|
|
goto requeue;
|
|
|
|
}
|
|
|
|
|
2013-04-23 15:31:39 +08:00
|
|
|
/* the interface is going down, skbs are purged */
|
2012-03-19 04:17:54 +08:00
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
return;
|
|
|
|
}
|
2013-04-23 15:31:39 +08:00
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
new_skb = netdev_alloc_skb_ip_align(ndev, cpsw->rx_packet_max);
|
2013-04-23 15:31:39 +08:00
|
|
|
if (new_skb) {
|
2016-08-23 02:18:26 +08:00
|
|
|
skb_copy_queue_mapping(new_skb, skb);
|
2012-03-19 04:17:54 +08:00
|
|
|
skb_put(skb, len);
|
2018-03-16 04:15:50 +08:00
|
|
|
if (status & CPDMA_RX_VLAN_ENCAP)
|
|
|
|
cpsw_rx_vlan_encap(skb);
|
2018-11-12 22:00:22 +08:00
|
|
|
priv = netdev_priv(ndev);
|
|
|
|
if (priv->rx_ts_enabled)
|
|
|
|
cpts_rx_timestamp(cpsw->cpts, skb);
|
2012-03-19 04:17:54 +08:00
|
|
|
skb->protocol = eth_type_trans(skb, ndev);
|
|
|
|
netif_receive_skb(skb);
|
2014-03-10 20:12:23 +08:00
|
|
|
ndev->stats.rx_bytes += len;
|
|
|
|
ndev->stats.rx_packets++;
|
2016-08-09 20:09:44 +08:00
|
|
|
kmemleak_not_leak(new_skb);
|
2013-04-23 15:31:39 +08:00
|
|
|
} else {
|
2014-03-10 20:12:23 +08:00
|
|
|
ndev->stats.rx_dropped++;
|
2013-04-23 15:31:39 +08:00
|
|
|
new_skb = skb;
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2014-09-10 19:08:09 +08:00
|
|
|
requeue:
|
2016-08-23 02:18:28 +08:00
|
|
|
if (netif_dormant(ndev)) {
|
|
|
|
dev_kfree_skb_any(new_skb);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-29 23:00:51 +08:00
|
|
|
ch = cpsw->rxv[skb_get_queue_mapping(new_skb)].ch;
|
2016-08-23 02:18:26 +08:00
|
|
|
ret = cpdma_chan_submit(ch, new_skb, new_skb->data,
|
2016-08-10 07:22:40 +08:00
|
|
|
skb_tailroom(new_skb), 0);
|
2013-04-23 15:31:39 +08:00
|
|
|
if (WARN_ON(ret < 0))
|
|
|
|
dev_kfree_skb_any(new_skb);
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:42 +08:00
|
|
|
void cpsw_split_res(struct cpsw_common *cpsw)
|
2016-12-06 09:45:00 +08:00
|
|
|
{
|
2016-12-10 20:23:48 +08:00
|
|
|
u32 consumed_rate = 0, bigest_rate = 0;
|
2016-12-06 09:45:00 +08:00
|
|
|
struct cpsw_vector *txv = cpsw->txv;
|
2016-12-10 20:23:48 +08:00
|
|
|
int i, ch_weight, rlim_ch_num = 0;
|
2016-12-06 09:45:00 +08:00
|
|
|
int budget, bigest_rate_ch = 0;
|
|
|
|
u32 ch_rate, max_rate;
|
|
|
|
int ch_budget = 0;
|
|
|
|
|
|
|
|
for (i = 0; i < cpsw->tx_ch_num; i++) {
|
|
|
|
ch_rate = cpdma_chan_get_rate(txv[i].ch);
|
|
|
|
if (!ch_rate)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
rlim_ch_num++;
|
|
|
|
consumed_rate += ch_rate;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cpsw->tx_ch_num == rlim_ch_num) {
|
|
|
|
max_rate = consumed_rate;
|
2016-12-10 20:23:48 +08:00
|
|
|
} else if (!rlim_ch_num) {
|
|
|
|
ch_budget = CPSW_POLL_WEIGHT / cpsw->tx_ch_num;
|
|
|
|
bigest_rate = 0;
|
|
|
|
max_rate = consumed_rate;
|
2016-12-06 09:45:00 +08:00
|
|
|
} else {
|
2016-12-10 20:23:49 +08:00
|
|
|
max_rate = cpsw->speed * 1000;
|
|
|
|
|
|
|
|
/* if max_rate is less then expected due to reduced link speed,
|
|
|
|
* split proportionally according next potential max speed
|
|
|
|
*/
|
|
|
|
if (max_rate < consumed_rate)
|
|
|
|
max_rate *= 10;
|
|
|
|
|
|
|
|
if (max_rate < consumed_rate)
|
|
|
|
max_rate *= 10;
|
2016-12-10 20:23:48 +08:00
|
|
|
|
2016-12-06 09:45:00 +08:00
|
|
|
ch_budget = (consumed_rate * CPSW_POLL_WEIGHT) / max_rate;
|
|
|
|
ch_budget = (CPSW_POLL_WEIGHT - ch_budget) /
|
|
|
|
(cpsw->tx_ch_num - rlim_ch_num);
|
|
|
|
bigest_rate = (max_rate - consumed_rate) /
|
|
|
|
(cpsw->tx_ch_num - rlim_ch_num);
|
|
|
|
}
|
|
|
|
|
2016-12-10 20:23:48 +08:00
|
|
|
/* split tx weight/budget */
|
2016-12-06 09:45:00 +08:00
|
|
|
budget = CPSW_POLL_WEIGHT;
|
|
|
|
for (i = 0; i < cpsw->tx_ch_num; i++) {
|
|
|
|
ch_rate = cpdma_chan_get_rate(txv[i].ch);
|
|
|
|
if (ch_rate) {
|
|
|
|
txv[i].budget = (ch_rate * CPSW_POLL_WEIGHT) / max_rate;
|
|
|
|
if (!txv[i].budget)
|
2016-12-10 20:23:48 +08:00
|
|
|
txv[i].budget++;
|
2016-12-06 09:45:00 +08:00
|
|
|
if (ch_rate > bigest_rate) {
|
|
|
|
bigest_rate_ch = i;
|
|
|
|
bigest_rate = ch_rate;
|
|
|
|
}
|
2016-12-10 20:23:48 +08:00
|
|
|
|
|
|
|
ch_weight = (ch_rate * 100) / max_rate;
|
|
|
|
if (!ch_weight)
|
|
|
|
ch_weight++;
|
|
|
|
cpdma_chan_set_weight(cpsw->txv[i].ch, ch_weight);
|
2016-12-06 09:45:00 +08:00
|
|
|
} else {
|
|
|
|
txv[i].budget = ch_budget;
|
|
|
|
if (!bigest_rate_ch)
|
|
|
|
bigest_rate_ch = i;
|
2016-12-10 20:23:48 +08:00
|
|
|
cpdma_chan_set_weight(cpsw->txv[i].ch, 0);
|
2016-12-06 09:45:00 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
budget -= txv[i].budget;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (budget)
|
|
|
|
txv[bigest_rate_ch].budget += budget;
|
|
|
|
|
|
|
|
/* split rx budget */
|
|
|
|
budget = CPSW_POLL_WEIGHT;
|
|
|
|
ch_budget = budget / cpsw->rx_ch_num;
|
|
|
|
for (i = 0; i < cpsw->rx_ch_num; i++) {
|
|
|
|
cpsw->rxv[i].budget = ch_budget;
|
|
|
|
budget -= ch_budget;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (budget)
|
|
|
|
cpsw->rxv[0].budget += budget;
|
|
|
|
}
|
|
|
|
|
2015-01-17 00:11:12 +08:00
|
|
|
static irqreturn_t cpsw_tx_interrupt(int irq, void *dev_id)
|
2012-03-19 04:17:54 +08:00
|
|
|
{
|
2016-08-10 07:22:43 +08:00
|
|
|
struct cpsw_common *cpsw = dev_id;
|
net: ethernet: cpsw: fix hangs with interrupts
The CPSW IP implements pulse-signaled interrupts. Due to
that we must write a correct, pre-defined value to the
CPDMA_MACEOIVECTOR register so the controller generates
a pulse on the correct IRQ line to signal the End Of
Interrupt.
The way the driver is written today, all four IRQ lines
are requested using the same IRQ handler and, because of
that, we could fall into situations where a TX IRQ fires
but we tell the controller that we ended an RX IRQ (or
vice-versa). This situation triggers an IRQ storm on the
reserved IRQ 127 of INTC which will in turn call ack_bad_irq()
which will, then, print a ton of:
unexpected IRQ trap at vector 00
In order to fix the problem, we are moving all calls to
cpdma_ctlr_eoi() inside the IRQ handler and making sure
we *always* write the correct value to the CPDMA_MACEOIVECTOR
register. Note that the algorithm assumes that IRQ numbers and
value-to-be-written-to-EOI are proportional, meaning that a
write of value 0 would trigger an EOI pulse for the RX_THRESHOLD
Interrupt and that's the IRQ number sitting in the 0-th index
of our irqs_table array.
This, however, is safe at least for current implementations of
CPSW so we will refrain from making the check smarter (and, as
a side-effect, slower) until we actually have a platform where
IRQ lines are swapped.
This patch has been tested for several days with AM335x- and
AM437x-based platforms. AM57x was left out because there are
still pending patches to enable ethernet in mainline for that
platform. A read of the TRM confirms the statement on previous
paragraph.
Reported-by: Yegor Yefremov <yegorslists@googlemail.com>
Fixes: 510a1e7 (drivers: net: davinci_cpdma: acknowledge interrupt properly)
Cc: <stable@vger.kernel.org> # v3.9+
Signed-off-by: Felipe Balbi <balbi@ti.com>
Acked-by: Tony Lindgren <tony@atomide.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2015-01-03 06:15:59 +08:00
|
|
|
|
2016-08-10 07:22:39 +08:00
|
|
|
writel(0, &cpsw->wr_regs->tx_en);
|
2016-08-10 07:22:40 +08:00
|
|
|
cpdma_ctlr_eoi(cpsw->dma, CPDMA_EOI_TX);
|
2015-01-17 00:11:12 +08:00
|
|
|
|
2016-08-10 07:22:41 +08:00
|
|
|
if (cpsw->quirk_irq) {
|
|
|
|
disable_irq_nosync(cpsw->irqs_table[1]);
|
|
|
|
cpsw->tx_irq_disabled = true;
|
2015-08-12 17:52:53 +08:00
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:43 +08:00
|
|
|
napi_schedule(&cpsw->napi_tx);
|
2015-01-17 00:11:12 +08:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t cpsw_rx_interrupt(int irq, void *dev_id)
|
|
|
|
{
|
2016-08-10 07:22:43 +08:00
|
|
|
struct cpsw_common *cpsw = dev_id;
|
2015-01-17 00:11:12 +08:00
|
|
|
|
2016-08-10 07:22:40 +08:00
|
|
|
cpdma_ctlr_eoi(cpsw->dma, CPDMA_EOI_RX);
|
2016-08-10 07:22:39 +08:00
|
|
|
writel(0, &cpsw->wr_regs->rx_en);
|
2013-04-23 15:31:37 +08:00
|
|
|
|
2016-08-10 07:22:41 +08:00
|
|
|
if (cpsw->quirk_irq) {
|
|
|
|
disable_irq_nosync(cpsw->irqs_table[0]);
|
|
|
|
cpsw->rx_irq_disabled = true;
|
2015-08-12 17:52:53 +08:00
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:43 +08:00
|
|
|
napi_schedule(&cpsw->napi_rx);
|
2015-08-04 18:36:19 +08:00
|
|
|
return IRQ_HANDLED;
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2018-05-17 06:21:45 +08:00
|
|
|
static int cpsw_tx_mq_poll(struct napi_struct *napi_tx, int budget)
|
2015-08-04 18:36:20 +08:00
|
|
|
{
|
2016-08-23 02:18:26 +08:00
|
|
|
u32 ch_map;
|
2016-11-29 23:00:51 +08:00
|
|
|
int num_tx, cur_budget, ch;
|
2016-08-10 07:22:43 +08:00
|
|
|
struct cpsw_common *cpsw = napi_to_cpsw(napi_tx);
|
2016-11-29 23:00:51 +08:00
|
|
|
struct cpsw_vector *txv;
|
2015-08-04 18:36:20 +08:00
|
|
|
|
2016-08-23 02:18:26 +08:00
|
|
|
/* process every unprocessed channel */
|
|
|
|
ch_map = cpdma_ctrl_txchs_state(cpsw->dma);
|
2018-07-24 05:26:29 +08:00
|
|
|
for (ch = 0, num_tx = 0; ch_map & 0xff; ch_map <<= 1, ch++) {
|
|
|
|
if (!(ch_map & 0x80))
|
2016-08-23 02:18:26 +08:00
|
|
|
continue;
|
|
|
|
|
2016-11-29 23:00:51 +08:00
|
|
|
txv = &cpsw->txv[ch];
|
|
|
|
if (unlikely(txv->budget > budget - num_tx))
|
|
|
|
cur_budget = budget - num_tx;
|
|
|
|
else
|
|
|
|
cur_budget = txv->budget;
|
|
|
|
|
|
|
|
num_tx += cpdma_chan_process(txv->ch, cur_budget);
|
2016-11-29 23:00:50 +08:00
|
|
|
if (num_tx >= budget)
|
|
|
|
break;
|
2016-08-23 02:18:26 +08:00
|
|
|
}
|
|
|
|
|
2015-08-04 18:36:20 +08:00
|
|
|
if (num_tx < budget) {
|
|
|
|
napi_complete(napi_tx);
|
2016-08-10 07:22:39 +08:00
|
|
|
writel(0xff, &cpsw->wr_regs->tx_en);
|
2018-05-17 06:21:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return num_tx;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_tx_poll(struct napi_struct *napi_tx, int budget)
|
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = napi_to_cpsw(napi_tx);
|
|
|
|
int num_tx;
|
|
|
|
|
|
|
|
num_tx = cpdma_chan_process(cpsw->txv[0].ch, budget);
|
|
|
|
if (num_tx < budget) {
|
|
|
|
napi_complete(napi_tx);
|
|
|
|
writel(0xff, &cpsw->wr_regs->tx_en);
|
|
|
|
if (cpsw->tx_irq_disabled) {
|
2016-08-10 07:22:41 +08:00
|
|
|
cpsw->tx_irq_disabled = false;
|
|
|
|
enable_irq(cpsw->irqs_table[1]);
|
2015-08-12 17:52:53 +08:00
|
|
|
}
|
2015-08-04 18:36:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return num_tx;
|
|
|
|
}
|
|
|
|
|
2018-05-17 06:21:45 +08:00
|
|
|
static int cpsw_rx_mq_poll(struct napi_struct *napi_rx, int budget)
|
2012-03-19 04:17:54 +08:00
|
|
|
{
|
2016-08-23 02:18:26 +08:00
|
|
|
u32 ch_map;
|
2016-11-29 23:00:51 +08:00
|
|
|
int num_rx, cur_budget, ch;
|
2016-08-10 07:22:43 +08:00
|
|
|
struct cpsw_common *cpsw = napi_to_cpsw(napi_rx);
|
2016-11-29 23:00:51 +08:00
|
|
|
struct cpsw_vector *rxv;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-08-23 02:18:26 +08:00
|
|
|
/* process every unprocessed channel */
|
|
|
|
ch_map = cpdma_ctrl_rxchs_state(cpsw->dma);
|
2016-11-29 23:00:50 +08:00
|
|
|
for (ch = 0, num_rx = 0; ch_map; ch_map >>= 1, ch++) {
|
2016-08-23 02:18:26 +08:00
|
|
|
if (!(ch_map & 0x01))
|
|
|
|
continue;
|
|
|
|
|
2016-11-29 23:00:51 +08:00
|
|
|
rxv = &cpsw->rxv[ch];
|
|
|
|
if (unlikely(rxv->budget > budget - num_rx))
|
|
|
|
cur_budget = budget - num_rx;
|
|
|
|
else
|
|
|
|
cur_budget = rxv->budget;
|
|
|
|
|
|
|
|
num_rx += cpdma_chan_process(rxv->ch, cur_budget);
|
2016-11-29 23:00:50 +08:00
|
|
|
if (num_rx >= budget)
|
|
|
|
break;
|
2016-08-23 02:18:26 +08:00
|
|
|
}
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
if (num_rx < budget) {
|
2017-01-31 00:22:01 +08:00
|
|
|
napi_complete_done(napi_rx, num_rx);
|
2016-08-10 07:22:39 +08:00
|
|
|
writel(0xff, &cpsw->wr_regs->rx_en);
|
2018-05-17 06:21:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return num_rx;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_rx_poll(struct napi_struct *napi_rx, int budget)
|
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = napi_to_cpsw(napi_rx);
|
|
|
|
int num_rx;
|
|
|
|
|
|
|
|
num_rx = cpdma_chan_process(cpsw->rxv[0].ch, budget);
|
|
|
|
if (num_rx < budget) {
|
|
|
|
napi_complete_done(napi_rx, num_rx);
|
|
|
|
writel(0xff, &cpsw->wr_regs->rx_en);
|
|
|
|
if (cpsw->rx_irq_disabled) {
|
2016-08-10 07:22:41 +08:00
|
|
|
cpsw->rx_irq_disabled = false;
|
|
|
|
enable_irq(cpsw->irqs_table[0]);
|
2015-08-12 17:52:53 +08:00
|
|
|
}
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return num_rx;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void soft_reset(const char *module, void __iomem *reg)
|
|
|
|
{
|
|
|
|
unsigned long timeout = jiffies + HZ;
|
|
|
|
|
2017-12-01 08:21:11 +08:00
|
|
|
writel_relaxed(1, reg);
|
2012-03-19 04:17:54 +08:00
|
|
|
do {
|
|
|
|
cpu_relax();
|
2017-12-01 08:21:11 +08:00
|
|
|
} while ((readl_relaxed(reg) & 1) && time_after(timeout, jiffies));
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2017-12-01 08:21:11 +08:00
|
|
|
WARN(readl_relaxed(reg) & 1, "failed to soft-reset %s\n", module);
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void cpsw_set_slave_mac(struct cpsw_slave *slave,
|
|
|
|
struct cpsw_priv *priv)
|
|
|
|
{
|
2012-10-29 16:45:15 +08:00
|
|
|
slave_write(slave, mac_hi(priv->mac_addr), SA_HI);
|
|
|
|
slave_write(slave, mac_lo(priv->mac_addr), SA_LO);
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
net: ethernet: ti: cpsw: add CBS Qdisc offload
The cpsw has up to 4 FIFOs per port and upper 3 FIFOs can feed rate
limited queue with shaping. In order to set and enable shaping for
those 3 FIFOs queues the network device with CBS qdisc attached is
needed. The CBS configuration is added for dual-emac/single port mode
only, but potentially can be used in switch mode also, based on
switchdev for instance.
Despite the FIFO shapers can work w/o cpdma level shapers the base
usage must be in combine with cpdma level shapers as described in TRM,
that are set as maximum rates for interface queues with sysfs.
One of the possible configuration with txq shapers and CBS shapers:
Configured with echo RATE >
/sys/class/net/eth0/queues/tx-0/tx_maxrate
/---------------------------------------------------
/
/ cpdma level shapers
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| c7 | | c6 | | c5 | | c4 | | c3 | | c2 | | c1 | | c0 |
\ / \ / \ / \ / \ / \ / \ / \ /
\ / \ / \ / \ / \ / \ / \ / \ /
\/ \/ \/ \/ \/ \/ \/ \/
+---------|------|------|------|-------------------------------------+
| +----+ | | +---+ |
| | +----+ | | |
| v v v v |
| +----+ +----+ +----+ +----+ p p+----+ +----+ +----+ +----+ |
| | | | | | | | | o o| | | | | | | | |
| | f3 | | f2 | | f1 | | f0 | r CPSW r| f3 | | f2 | | f1 | | f0 | |
| | | | | | | | | t t| | | | | | | | |
| \ / \ / \ / \ / 0 1\ / \ / \ / \ / |
| \ X \ / \ / \ / \ / \ / \ / \ / |
| \/ \ \/ \/ \/ \/ \/ \/ \/ |
+-------\------------------------------------------------------------+
\
\ FIFO shaper, set with CBS offload added in this patch,
\ FIFO0 cannot be rate limited
------------------------------------------------------
CBS shaper configuration is supposed to be used with root MQPRIO Qdisc
offload allowing to add sk_prio->tc->txq maps that direct traffic to
appropriate tx queue and maps L2 priority to FIFO shaper.
The CBS shaper is intended to be used for AVB where L2 priority
(pcp field) is used to differentiate class of traffic. So additionally
vlan needs to be created with appropriate egress sk_prio->l2 prio map.
If CBS has several tx queues assigned to it, the sum of their
bandwidth has not overlap bandwidth set for CBS. It's recomended the
CBS bandwidth to be a little bit more.
The CBS shaper is configured with CBS qdisc offload interface using tc
tool from iproute2 packet.
For instance:
$ tc qdisc replace dev eth0 handle 100: parent root mqprio num_tc 3 \
map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 1
$ tc -g class show dev eth0
+---(100:ffe2) mqprio
| +---(100:3) mqprio
| +---(100:4) mqprio
|
+---(100:ffe1) mqprio
| +---(100:2) mqprio
|
+---(100:ffe0) mqprio
+---(100:1) mqprio
$ tc qdisc add dev eth0 parent 100:1 cbs locredit -1440 \
hicredit 60 sendslope -960000 idleslope 40000 offload 1
$ tc qdisc add dev eth0 parent 100:2 cbs locredit -1470 \
hicredit 62 sendslope -980000 idleslope 20000 offload 1
The above code set CBS shapers for tc0 and tc1, for that txq0 and
txq1 is used. Pay attention, the real set bandwidth can differ a bit
due to discreteness of configuration parameters.
Here parameters like locredit, hicredit and sendslope are ignored
internally and are supposed to be set with assumption that maximum
frame size for frame - 1500.
It's supposed that interface speed is not changed while reconnection,
not always is true, so inform user in case speed of interface was
changed, as it can impact on dependent shapers configuration.
For more examples see Documentation.
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-07-24 05:26:32 +08:00
|
|
|
static bool cpsw_shp_is_off(struct cpsw_priv *priv)
|
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
struct cpsw_slave *slave;
|
|
|
|
u32 shift, mask, val;
|
|
|
|
|
|
|
|
val = readl_relaxed(&cpsw->regs->ptype);
|
|
|
|
|
|
|
|
slave = &cpsw->slaves[cpsw_slave_index(cpsw, priv)];
|
|
|
|
shift = CPSW_FIFO_SHAPE_EN_SHIFT + 3 * slave->slave_num;
|
|
|
|
mask = 7 << shift;
|
|
|
|
val = val & mask;
|
|
|
|
|
|
|
|
return !val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cpsw_fifo_shp_on(struct cpsw_priv *priv, int fifo, int on)
|
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
struct cpsw_slave *slave;
|
|
|
|
u32 shift, mask, val;
|
|
|
|
|
|
|
|
val = readl_relaxed(&cpsw->regs->ptype);
|
|
|
|
|
|
|
|
slave = &cpsw->slaves[cpsw_slave_index(cpsw, priv)];
|
|
|
|
shift = CPSW_FIFO_SHAPE_EN_SHIFT + 3 * slave->slave_num;
|
|
|
|
mask = (1 << --fifo) << shift;
|
|
|
|
val = on ? val | mask : val & ~mask;
|
|
|
|
|
|
|
|
writel_relaxed(val, &cpsw->regs->ptype);
|
|
|
|
}
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
static void _cpsw_adjust_link(struct cpsw_slave *slave,
|
|
|
|
struct cpsw_priv *priv, bool *link)
|
|
|
|
{
|
|
|
|
struct phy_device *phy = slave->phy;
|
|
|
|
u32 mac_control = 0;
|
|
|
|
u32 slave_port;
|
2016-08-10 07:22:42 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
if (!phy)
|
|
|
|
return;
|
|
|
|
|
2016-08-10 07:22:34 +08:00
|
|
|
slave_port = cpsw_get_slave_port(slave->slave_num);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
if (phy->link) {
|
2019-04-27 01:12:41 +08:00
|
|
|
mac_control = CPSW_SL_CTL_GMII_EN;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
if (phy->speed == 1000)
|
2019-04-27 01:12:41 +08:00
|
|
|
mac_control |= CPSW_SL_CTL_GIG;
|
2012-03-19 04:17:54 +08:00
|
|
|
if (phy->duplex)
|
2019-04-27 01:12:41 +08:00
|
|
|
mac_control |= CPSW_SL_CTL_FULLDUPLEX;
|
2012-09-27 17:19:34 +08:00
|
|
|
|
|
|
|
/* set speed_in input in case RMII mode is used in 100Mbps */
|
|
|
|
if (phy->speed == 100)
|
2019-04-27 01:12:41 +08:00
|
|
|
mac_control |= CPSW_SL_CTL_IFCTL_A;
|
2018-03-16 00:56:01 +08:00
|
|
|
/* in band mode only works in 10Mbps RGMII mode */
|
|
|
|
else if ((phy->speed == 10) && phy_interface_is_rgmii(phy))
|
2019-04-27 01:12:41 +08:00
|
|
|
mac_control |= CPSW_SL_CTL_EXT_EN; /* In Band mode */
|
2012-09-27 17:19:34 +08:00
|
|
|
|
2014-09-09 01:24:02 +08:00
|
|
|
if (priv->rx_pause)
|
2019-04-27 01:12:41 +08:00
|
|
|
mac_control |= CPSW_SL_CTL_RX_FLOW_EN;
|
2014-09-09 01:24:02 +08:00
|
|
|
|
|
|
|
if (priv->tx_pause)
|
2019-04-27 01:12:41 +08:00
|
|
|
mac_control |= CPSW_SL_CTL_TX_FLOW_EN;
|
|
|
|
|
|
|
|
if (mac_control != slave->mac_control)
|
|
|
|
cpsw_sl_ctl_set(slave->mac_sl, mac_control);
|
|
|
|
|
|
|
|
/* enable forwarding */
|
|
|
|
cpsw_ale_control_set(cpsw->ale, slave_port,
|
|
|
|
ALE_PORT_STATE, ALE_PORT_STATE_FORWARD);
|
2014-09-09 01:24:02 +08:00
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
*link = true;
|
net: ethernet: ti: cpsw: add CBS Qdisc offload
The cpsw has up to 4 FIFOs per port and upper 3 FIFOs can feed rate
limited queue with shaping. In order to set and enable shaping for
those 3 FIFOs queues the network device with CBS qdisc attached is
needed. The CBS configuration is added for dual-emac/single port mode
only, but potentially can be used in switch mode also, based on
switchdev for instance.
Despite the FIFO shapers can work w/o cpdma level shapers the base
usage must be in combine with cpdma level shapers as described in TRM,
that are set as maximum rates for interface queues with sysfs.
One of the possible configuration with txq shapers and CBS shapers:
Configured with echo RATE >
/sys/class/net/eth0/queues/tx-0/tx_maxrate
/---------------------------------------------------
/
/ cpdma level shapers
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| c7 | | c6 | | c5 | | c4 | | c3 | | c2 | | c1 | | c0 |
\ / \ / \ / \ / \ / \ / \ / \ /
\ / \ / \ / \ / \ / \ / \ / \ /
\/ \/ \/ \/ \/ \/ \/ \/
+---------|------|------|------|-------------------------------------+
| +----+ | | +---+ |
| | +----+ | | |
| v v v v |
| +----+ +----+ +----+ +----+ p p+----+ +----+ +----+ +----+ |
| | | | | | | | | o o| | | | | | | | |
| | f3 | | f2 | | f1 | | f0 | r CPSW r| f3 | | f2 | | f1 | | f0 | |
| | | | | | | | | t t| | | | | | | | |
| \ / \ / \ / \ / 0 1\ / \ / \ / \ / |
| \ X \ / \ / \ / \ / \ / \ / \ / |
| \/ \ \/ \/ \/ \/ \/ \/ \/ |
+-------\------------------------------------------------------------+
\
\ FIFO shaper, set with CBS offload added in this patch,
\ FIFO0 cannot be rate limited
------------------------------------------------------
CBS shaper configuration is supposed to be used with root MQPRIO Qdisc
offload allowing to add sk_prio->tc->txq maps that direct traffic to
appropriate tx queue and maps L2 priority to FIFO shaper.
The CBS shaper is intended to be used for AVB where L2 priority
(pcp field) is used to differentiate class of traffic. So additionally
vlan needs to be created with appropriate egress sk_prio->l2 prio map.
If CBS has several tx queues assigned to it, the sum of their
bandwidth has not overlap bandwidth set for CBS. It's recomended the
CBS bandwidth to be a little bit more.
The CBS shaper is configured with CBS qdisc offload interface using tc
tool from iproute2 packet.
For instance:
$ tc qdisc replace dev eth0 handle 100: parent root mqprio num_tc 3 \
map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 1
$ tc -g class show dev eth0
+---(100:ffe2) mqprio
| +---(100:3) mqprio
| +---(100:4) mqprio
|
+---(100:ffe1) mqprio
| +---(100:2) mqprio
|
+---(100:ffe0) mqprio
+---(100:1) mqprio
$ tc qdisc add dev eth0 parent 100:1 cbs locredit -1440 \
hicredit 60 sendslope -960000 idleslope 40000 offload 1
$ tc qdisc add dev eth0 parent 100:2 cbs locredit -1470 \
hicredit 62 sendslope -980000 idleslope 20000 offload 1
The above code set CBS shapers for tc0 and tc1, for that txq0 and
txq1 is used. Pay attention, the real set bandwidth can differ a bit
due to discreteness of configuration parameters.
Here parameters like locredit, hicredit and sendslope are ignored
internally and are supposed to be set with assumption that maximum
frame size for frame - 1500.
It's supposed that interface speed is not changed while reconnection,
not always is true, so inform user in case speed of interface was
changed, as it can impact on dependent shapers configuration.
For more examples see Documentation.
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-07-24 05:26:32 +08:00
|
|
|
|
|
|
|
if (priv->shp_cfg_speed &&
|
|
|
|
priv->shp_cfg_speed != slave->phy->speed &&
|
|
|
|
!cpsw_shp_is_off(priv))
|
|
|
|
dev_warn(priv->dev,
|
|
|
|
"Speed was changed, CBS shaper speeds are changed!");
|
2012-03-19 04:17:54 +08:00
|
|
|
} else {
|
|
|
|
mac_control = 0;
|
|
|
|
/* disable forwarding */
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_control_set(cpsw->ale, slave_port,
|
2012-03-19 04:17:54 +08:00
|
|
|
ALE_PORT_STATE, ALE_PORT_STATE_DISABLE);
|
2019-04-27 01:12:41 +08:00
|
|
|
|
|
|
|
cpsw_sl_wait_for_idle(slave->mac_sl, 100);
|
|
|
|
|
|
|
|
cpsw_sl_ctl_reset(slave->mac_sl);
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:41 +08:00
|
|
|
if (mac_control != slave->mac_control)
|
2012-03-19 04:17:54 +08:00
|
|
|
phy_print_status(phy);
|
|
|
|
|
|
|
|
slave->mac_control = mac_control;
|
|
|
|
}
|
|
|
|
|
2016-12-10 20:23:49 +08:00
|
|
|
static int cpsw_get_common_speed(struct cpsw_common *cpsw)
|
|
|
|
{
|
|
|
|
int i, speed;
|
|
|
|
|
|
|
|
for (i = 0, speed = 0; i < cpsw->data.slaves; i++)
|
|
|
|
if (cpsw->slaves[i].phy && cpsw->slaves[i].phy->link)
|
|
|
|
speed += cpsw->slaves[i].phy->speed;
|
|
|
|
|
|
|
|
return speed;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_need_resplit(struct cpsw_common *cpsw)
|
|
|
|
{
|
|
|
|
int i, rlim_ch_num;
|
|
|
|
int speed, ch_rate;
|
|
|
|
|
|
|
|
/* re-split resources only in case speed was changed */
|
|
|
|
speed = cpsw_get_common_speed(cpsw);
|
|
|
|
if (speed == cpsw->speed || !speed)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
cpsw->speed = speed;
|
|
|
|
|
|
|
|
for (i = 0, rlim_ch_num = 0; i < cpsw->tx_ch_num; i++) {
|
|
|
|
ch_rate = cpdma_chan_get_rate(cpsw->txv[i].ch);
|
|
|
|
if (!ch_rate)
|
|
|
|
break;
|
|
|
|
|
|
|
|
rlim_ch_num++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* cases not dependent on speed */
|
|
|
|
if (!rlim_ch_num || rlim_ch_num == cpsw->tx_ch_num)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
static void cpsw_adjust_link(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
2016-12-10 20:23:49 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2012-03-19 04:17:54 +08:00
|
|
|
bool link = false;
|
|
|
|
|
|
|
|
for_each_slave(priv, _cpsw_adjust_link, priv, &link);
|
|
|
|
|
|
|
|
if (link) {
|
2016-12-10 20:23:49 +08:00
|
|
|
if (cpsw_need_resplit(cpsw))
|
2019-04-27 01:12:26 +08:00
|
|
|
cpsw_split_res(cpsw);
|
2016-12-10 20:23:49 +08:00
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
netif_carrier_on(ndev);
|
|
|
|
if (netif_running(ndev))
|
2016-08-23 02:18:26 +08:00
|
|
|
netif_tx_wake_all_queues(ndev);
|
2012-03-19 04:17:54 +08:00
|
|
|
} else {
|
|
|
|
netif_carrier_off(ndev);
|
2016-08-23 02:18:26 +08:00
|
|
|
netif_tx_stop_all_queues(ndev);
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-11 17:52:20 +08:00
|
|
|
static inline void cpsw_add_dual_emac_def_ale_entries(
|
|
|
|
struct cpsw_priv *priv, struct cpsw_slave *slave,
|
|
|
|
u32 slave_port)
|
|
|
|
{
|
2016-08-10 07:22:44 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2016-04-07 20:16:44 +08:00
|
|
|
u32 port_mask = 1 << slave_port | ALE_PORT_HOST;
|
2013-02-11 17:52:20 +08:00
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
if (cpsw->version == CPSW_VERSION_1)
|
2013-02-11 17:52:20 +08:00
|
|
|
slave_write(slave, slave->port_vlan, CPSW1_PORT_VLAN);
|
|
|
|
else
|
|
|
|
slave_write(slave, slave->port_vlan, CPSW2_PORT_VLAN);
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_vlan(cpsw->ale, slave->port_vlan, port_mask,
|
2013-02-11 17:52:20 +08:00
|
|
|
port_mask, port_mask, 0);
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast,
|
net: ethernet: ti: cpsw: use for mcast entries only host port
In dual-emac mode the cpsw driver sends directed packets, that means
that packets go to the directed port, but an ALE lookup is performed
to determine untagged egress only. It means that on tx side no need
to add port bit for ALE mcast entry mask, and basically ALE entry
for port identification is needed only on rx side.
So, add only host port in dual_emac mode as used directed
transmission, and no need in one more port. For single port boards
and switch mode all ports used, as usual, so no changes for them.
Also it simplifies farther changes.
In other words, mcast entries for dual-emac should behave exactly
like unicast. It also can help avoid leaking packets between ports
with same vlan on h/w level if ports could became members of same vid.
So now, for instance, if mcast address 33:33:00:00:00:01 is added then
entries in ALE table:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
Instead of:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x3
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x5
With the same considerations, set only host port for unregistered
mcast for dual-emac mode in case of IFF_ALLMULTI is set, exactly like
it's done in cpsw_ale_set_allmulti().
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-10-13 00:06:29 +08:00
|
|
|
ALE_PORT_HOST, ALE_VLAN, slave->port_vlan, 0);
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr,
|
|
|
|
HOST_PORT_NUM, ALE_VLAN |
|
|
|
|
ALE_SECURE, slave->port_vlan);
|
2018-05-02 01:41:22 +08:00
|
|
|
cpsw_ale_control_set(cpsw->ale, slave_port,
|
|
|
|
ALE_PORT_DROP_UNKNOWN_VLAN, 1);
|
2013-02-11 17:52:20 +08:00
|
|
|
}
|
|
|
|
|
2013-11-15 15:29:16 +08:00
|
|
|
static void cpsw_slave_open(struct cpsw_slave *slave, struct cpsw_priv *priv)
|
|
|
|
{
|
|
|
|
u32 slave_port;
|
2017-04-03 20:04:28 +08:00
|
|
|
struct phy_device *phy;
|
2016-08-10 07:22:37 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2013-11-15 15:29:16 +08:00
|
|
|
|
2019-04-27 01:12:41 +08:00
|
|
|
cpsw_sl_reset(slave->mac_sl, 100);
|
|
|
|
cpsw_sl_ctl_reset(slave->mac_sl);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
/* setup priority mapping */
|
2019-04-27 01:12:41 +08:00
|
|
|
cpsw_sl_reg_write(slave->mac_sl, CPSW_SL_RX_PRI_MAP,
|
|
|
|
RX_PRIORITY_MAPPING);
|
2012-10-29 16:45:15 +08:00
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
switch (cpsw->version) {
|
2012-10-29 16:45:15 +08:00
|
|
|
case CPSW_VERSION_1:
|
|
|
|
slave_write(slave, TX_PRIORITY_MAPPING, CPSW1_TX_PRI_MAP);
|
2017-05-09 03:21:21 +08:00
|
|
|
/* Increase RX FIFO size to 5 for supporting fullduplex
|
|
|
|
* flow control mode
|
|
|
|
*/
|
|
|
|
slave_write(slave,
|
|
|
|
(CPSW_MAX_BLKS_TX << CPSW_MAX_BLKS_TX_SHIFT) |
|
|
|
|
CPSW_MAX_BLKS_RX, CPSW1_MAX_BLKS);
|
2012-10-29 16:45:15 +08:00
|
|
|
break;
|
|
|
|
case CPSW_VERSION_2:
|
2013-08-05 20:00:05 +08:00
|
|
|
case CPSW_VERSION_3:
|
2013-08-12 19:41:15 +08:00
|
|
|
case CPSW_VERSION_4:
|
2012-10-29 16:45:15 +08:00
|
|
|
slave_write(slave, TX_PRIORITY_MAPPING, CPSW2_TX_PRI_MAP);
|
2017-05-09 03:21:21 +08:00
|
|
|
/* Increase RX FIFO size to 5 for supporting fullduplex
|
|
|
|
* flow control mode
|
|
|
|
*/
|
|
|
|
slave_write(slave,
|
|
|
|
(CPSW_MAX_BLKS_TX << CPSW_MAX_BLKS_TX_SHIFT) |
|
|
|
|
CPSW_MAX_BLKS_RX, CPSW2_MAX_BLKS);
|
2012-10-29 16:45:15 +08:00
|
|
|
break;
|
|
|
|
}
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
/* setup max packet size, and mac address */
|
2019-04-27 01:12:41 +08:00
|
|
|
cpsw_sl_reg_write(slave->mac_sl, CPSW_SL_RX_MAXLEN,
|
|
|
|
cpsw->rx_packet_max);
|
2012-03-19 04:17:54 +08:00
|
|
|
cpsw_set_slave_mac(slave, priv);
|
|
|
|
|
|
|
|
slave->mac_control = 0; /* no link yet */
|
|
|
|
|
2016-08-10 07:22:34 +08:00
|
|
|
slave_port = cpsw_get_slave_port(slave->slave_num);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac)
|
2013-02-11 17:52:20 +08:00
|
|
|
cpsw_add_dual_emac_def_ale_entries(priv, slave, slave_port);
|
|
|
|
else
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast,
|
2013-02-11 17:52:20 +08:00
|
|
|
1 << slave_port, 0, 0, ALE_MCAST_FWD_2);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-04-28 09:32:31 +08:00
|
|
|
if (slave->data->phy_node) {
|
2017-04-03 20:04:28 +08:00
|
|
|
phy = of_phy_connect(priv->ndev, slave->data->phy_node,
|
2015-10-17 12:04:35 +08:00
|
|
|
&cpsw_adjust_link, 0, slave->data->phy_if);
|
2017-04-03 20:04:28 +08:00
|
|
|
if (!phy) {
|
2017-07-19 05:43:19 +08:00
|
|
|
dev_err(priv->dev, "phy \"%pOF\" not found on slave %d\n",
|
|
|
|
slave->data->phy_node,
|
2016-04-28 09:32:31 +08:00
|
|
|
slave->slave_num);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
2017-04-03 20:04:28 +08:00
|
|
|
phy = phy_connect(priv->ndev, slave->data->phy_id,
|
2013-01-14 08:52:52 +08:00
|
|
|
&cpsw_adjust_link, slave->data->phy_if);
|
2017-04-03 20:04:28 +08:00
|
|
|
if (IS_ERR(phy)) {
|
2016-04-28 09:32:31 +08:00
|
|
|
dev_err(priv->dev,
|
|
|
|
"phy \"%s\" not found on slave %d, err %ld\n",
|
|
|
|
slave->data->phy_id, slave->slave_num,
|
2017-04-03 20:04:28 +08:00
|
|
|
PTR_ERR(phy));
|
2016-04-28 09:32:31 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2016-01-07 03:11:13 +08:00
|
|
|
|
2017-04-03 20:04:28 +08:00
|
|
|
slave->phy = phy;
|
|
|
|
|
2016-04-28 09:32:31 +08:00
|
|
|
phy_attached_info(slave->phy);
|
2013-09-21 03:20:40 +08:00
|
|
|
|
2016-04-28 09:32:31 +08:00
|
|
|
phy_start(slave->phy);
|
|
|
|
|
|
|
|
/* Configure GMII_SEL register */
|
2018-11-26 08:15:25 +08:00
|
|
|
if (!IS_ERR(slave->data->ifphy))
|
|
|
|
phy_set_mode_ext(slave->data->ifphy, PHY_MODE_ETHERNET,
|
|
|
|
slave->data->phy_if);
|
|
|
|
else
|
|
|
|
cpsw_phy_sel(cpsw->dev, slave->phy->interface,
|
|
|
|
slave->slave_num);
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2013-02-05 16:26:48 +08:00
|
|
|
static inline void cpsw_add_default_vlan(struct cpsw_priv *priv)
|
|
|
|
{
|
2016-08-10 07:22:42 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
const int vlan = cpsw->data.default_vlan;
|
2013-02-05 16:26:48 +08:00
|
|
|
u32 reg;
|
|
|
|
int i;
|
2014-11-01 01:38:52 +08:00
|
|
|
int unreg_mcast_mask;
|
2013-02-05 16:26:48 +08:00
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
reg = (cpsw->version == CPSW_VERSION_1) ? CPSW1_PORT_VLAN :
|
2013-02-05 16:26:48 +08:00
|
|
|
CPSW2_PORT_VLAN;
|
|
|
|
|
2016-08-10 07:22:39 +08:00
|
|
|
writel(vlan, &cpsw->host_port_regs->port_vlan);
|
2013-02-05 16:26:48 +08:00
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
for (i = 0; i < cpsw->data.slaves; i++)
|
|
|
|
slave_write(cpsw->slaves + i, vlan, reg);
|
2013-02-05 16:26:48 +08:00
|
|
|
|
2014-11-01 01:38:52 +08:00
|
|
|
if (priv->ndev->flags & IFF_ALLMULTI)
|
|
|
|
unreg_mcast_mask = ALE_ALL_PORTS;
|
|
|
|
else
|
|
|
|
unreg_mcast_mask = ALE_PORT_1 | ALE_PORT_2;
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_vlan(cpsw->ale, vlan, ALE_ALL_PORTS,
|
drivers: net: cpsw: fix port_mask parameters in ale calls
ALE APIs expect to receive port masks as input values for arguments
port_mask, untag, reg_mcast, unreg_mcast. But there are few places in
code where port masks are passed left-shifted by cpsw_priv->host_port,
like below:
cpsw_ale_add_vlan(priv->ale, priv->data.default_vlan,
ALE_ALL_PORTS << priv->host_port,
ALE_ALL_PORTS << priv->host_port, 0, 0);
and cpsw is still working just because priv->host_port == 0
and has never ever been changed.
Hence, fix port_mask parameters in ALE APIs calls and drop
"<< priv->host_port" from all places where it's used to
shift valid port mask.
Signed-off-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-04-07 20:16:43 +08:00
|
|
|
ALE_ALL_PORTS, ALE_ALL_PORTS,
|
|
|
|
unreg_mcast_mask);
|
2013-02-05 16:26:48 +08:00
|
|
|
}
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
static void cpsw_init_host_port(struct cpsw_priv *priv)
|
|
|
|
{
|
2013-02-11 17:52:20 +08:00
|
|
|
u32 fifo_mode;
|
2016-08-10 07:22:39 +08:00
|
|
|
u32 control_reg;
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2013-02-05 16:26:48 +08:00
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
/* soft reset the controller and initialize ale */
|
2016-08-10 07:22:39 +08:00
|
|
|
soft_reset("cpsw", &cpsw->regs->soft_reset);
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_start(cpsw->ale);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
/* switch to vlan unaware mode */
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM, ALE_VLAN_AWARE,
|
2013-02-05 16:26:48 +08:00
|
|
|
CPSW_ALE_VLAN_AWARE);
|
2016-08-10 07:22:39 +08:00
|
|
|
control_reg = readl(&cpsw->regs->control);
|
2018-03-16 04:15:50 +08:00
|
|
|
control_reg |= CPSW_VLAN_AWARE | CPSW_RX_VLAN_ENCAP;
|
2016-08-10 07:22:39 +08:00
|
|
|
writel(control_reg, &cpsw->regs->control);
|
2016-08-10 07:22:42 +08:00
|
|
|
fifo_mode = (cpsw->data.dual_emac) ? CPSW_FIFO_DUAL_MAC_MODE :
|
2013-02-11 17:52:20 +08:00
|
|
|
CPSW_FIFO_NORMAL_MODE;
|
2016-08-10 07:22:39 +08:00
|
|
|
writel(fifo_mode, &cpsw->host_port_regs->tx_in_ctl);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
/* setup host port priority mapping */
|
2017-12-01 08:21:11 +08:00
|
|
|
writel_relaxed(CPDMA_TX_PRIORITY_MAP,
|
|
|
|
&cpsw->host_port_regs->cpdma_tx_pri_map);
|
|
|
|
writel_relaxed(0, &cpsw->host_port_regs->cpdma_rx_chan_map);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_control_set(cpsw->ale, HOST_PORT_NUM,
|
2012-03-19 04:17:54 +08:00
|
|
|
ALE_PORT_STATE, ALE_PORT_STATE_FORWARD);
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (!cpsw->data.dual_emac) {
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM,
|
2013-02-11 17:52:20 +08:00
|
|
|
0, 0);
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast,
|
2016-04-07 20:16:44 +08:00
|
|
|
ALE_PORT_HOST, 0, 0, ALE_MCAST_FWD_2);
|
2013-02-11 17:52:20 +08:00
|
|
|
}
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:42 +08:00
|
|
|
int cpsw_fill_rx_channels(struct cpsw_priv *priv)
|
2016-08-23 02:18:24 +08:00
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
struct sk_buff *skb;
|
|
|
|
int ch_buf_num;
|
2016-08-23 02:18:26 +08:00
|
|
|
int ch, i, ret;
|
|
|
|
|
|
|
|
for (ch = 0; ch < cpsw->rx_ch_num; ch++) {
|
2016-11-29 23:00:51 +08:00
|
|
|
ch_buf_num = cpdma_chan_get_rx_buf_num(cpsw->rxv[ch].ch);
|
2016-08-23 02:18:26 +08:00
|
|
|
for (i = 0; i < ch_buf_num; i++) {
|
|
|
|
skb = __netdev_alloc_skb_ip_align(priv->ndev,
|
|
|
|
cpsw->rx_packet_max,
|
|
|
|
GFP_KERNEL);
|
|
|
|
if (!skb) {
|
|
|
|
cpsw_err(priv, ifup, "cannot allocate skb\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
2016-08-23 02:18:24 +08:00
|
|
|
|
2016-08-23 02:18:26 +08:00
|
|
|
skb_set_queue_mapping(skb, ch);
|
2016-11-29 23:00:51 +08:00
|
|
|
ret = cpdma_chan_submit(cpsw->rxv[ch].ch, skb,
|
|
|
|
skb->data, skb_tailroom(skb),
|
|
|
|
0);
|
2016-08-23 02:18:26 +08:00
|
|
|
if (ret < 0) {
|
|
|
|
cpsw_err(priv, ifup,
|
|
|
|
"cannot submit skb to channel %d rx, error %d\n",
|
|
|
|
ch, ret);
|
|
|
|
kfree_skb(skb);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
kmemleak_not_leak(skb);
|
2016-08-23 02:18:24 +08:00
|
|
|
}
|
|
|
|
|
2016-08-23 02:18:26 +08:00
|
|
|
cpsw_info(priv, ifup, "ch %d rx, submitted %d descriptors\n",
|
|
|
|
ch, ch_buf_num);
|
|
|
|
}
|
2016-08-23 02:18:24 +08:00
|
|
|
|
2016-08-23 02:18:26 +08:00
|
|
|
return 0;
|
2016-08-23 02:18:24 +08:00
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
static void cpsw_slave_stop(struct cpsw_slave *slave, struct cpsw_common *cpsw)
|
2013-04-23 15:31:36 +08:00
|
|
|
{
|
2014-03-03 18:49:06 +08:00
|
|
|
u32 slave_port;
|
|
|
|
|
2016-08-10 07:22:34 +08:00
|
|
|
slave_port = cpsw_get_slave_port(slave->slave_num);
|
2014-03-03 18:49:06 +08:00
|
|
|
|
2013-04-23 15:31:36 +08:00
|
|
|
if (!slave->phy)
|
|
|
|
return;
|
|
|
|
phy_stop(slave->phy);
|
|
|
|
phy_disconnect(slave->phy);
|
|
|
|
slave->phy = NULL;
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_control_set(cpsw->ale, slave_port,
|
2014-03-03 18:49:06 +08:00
|
|
|
ALE_PORT_STATE, ALE_PORT_STATE_DISABLE);
|
2019-04-27 01:12:41 +08:00
|
|
|
cpsw_sl_reset(slave->mac_sl, 100);
|
|
|
|
cpsw_sl_ctl_reset(slave->mac_sl);
|
2013-04-23 15:31:36 +08:00
|
|
|
}
|
|
|
|
|
2018-07-24 05:26:31 +08:00
|
|
|
static int cpsw_tc_to_fifo(int tc, int num_tc)
|
|
|
|
{
|
|
|
|
if (tc == num_tc - 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return CPSW_FIFO_SHAPERS_NUM - tc;
|
|
|
|
}
|
|
|
|
|
net: ethernet: ti: cpsw: add CBS Qdisc offload
The cpsw has up to 4 FIFOs per port and upper 3 FIFOs can feed rate
limited queue with shaping. In order to set and enable shaping for
those 3 FIFOs queues the network device with CBS qdisc attached is
needed. The CBS configuration is added for dual-emac/single port mode
only, but potentially can be used in switch mode also, based on
switchdev for instance.
Despite the FIFO shapers can work w/o cpdma level shapers the base
usage must be in combine with cpdma level shapers as described in TRM,
that are set as maximum rates for interface queues with sysfs.
One of the possible configuration with txq shapers and CBS shapers:
Configured with echo RATE >
/sys/class/net/eth0/queues/tx-0/tx_maxrate
/---------------------------------------------------
/
/ cpdma level shapers
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| c7 | | c6 | | c5 | | c4 | | c3 | | c2 | | c1 | | c0 |
\ / \ / \ / \ / \ / \ / \ / \ /
\ / \ / \ / \ / \ / \ / \ / \ /
\/ \/ \/ \/ \/ \/ \/ \/
+---------|------|------|------|-------------------------------------+
| +----+ | | +---+ |
| | +----+ | | |
| v v v v |
| +----+ +----+ +----+ +----+ p p+----+ +----+ +----+ +----+ |
| | | | | | | | | o o| | | | | | | | |
| | f3 | | f2 | | f1 | | f0 | r CPSW r| f3 | | f2 | | f1 | | f0 | |
| | | | | | | | | t t| | | | | | | | |
| \ / \ / \ / \ / 0 1\ / \ / \ / \ / |
| \ X \ / \ / \ / \ / \ / \ / \ / |
| \/ \ \/ \/ \/ \/ \/ \/ \/ |
+-------\------------------------------------------------------------+
\
\ FIFO shaper, set with CBS offload added in this patch,
\ FIFO0 cannot be rate limited
------------------------------------------------------
CBS shaper configuration is supposed to be used with root MQPRIO Qdisc
offload allowing to add sk_prio->tc->txq maps that direct traffic to
appropriate tx queue and maps L2 priority to FIFO shaper.
The CBS shaper is intended to be used for AVB where L2 priority
(pcp field) is used to differentiate class of traffic. So additionally
vlan needs to be created with appropriate egress sk_prio->l2 prio map.
If CBS has several tx queues assigned to it, the sum of their
bandwidth has not overlap bandwidth set for CBS. It's recomended the
CBS bandwidth to be a little bit more.
The CBS shaper is configured with CBS qdisc offload interface using tc
tool from iproute2 packet.
For instance:
$ tc qdisc replace dev eth0 handle 100: parent root mqprio num_tc 3 \
map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 1
$ tc -g class show dev eth0
+---(100:ffe2) mqprio
| +---(100:3) mqprio
| +---(100:4) mqprio
|
+---(100:ffe1) mqprio
| +---(100:2) mqprio
|
+---(100:ffe0) mqprio
+---(100:1) mqprio
$ tc qdisc add dev eth0 parent 100:1 cbs locredit -1440 \
hicredit 60 sendslope -960000 idleslope 40000 offload 1
$ tc qdisc add dev eth0 parent 100:2 cbs locredit -1470 \
hicredit 62 sendslope -980000 idleslope 20000 offload 1
The above code set CBS shapers for tc0 and tc1, for that txq0 and
txq1 is used. Pay attention, the real set bandwidth can differ a bit
due to discreteness of configuration parameters.
Here parameters like locredit, hicredit and sendslope are ignored
internally and are supposed to be set with assumption that maximum
frame size for frame - 1500.
It's supposed that interface speed is not changed while reconnection,
not always is true, so inform user in case speed of interface was
changed, as it can impact on dependent shapers configuration.
For more examples see Documentation.
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-07-24 05:26:32 +08:00
|
|
|
static int cpsw_set_fifo_bw(struct cpsw_priv *priv, int fifo, int bw)
|
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
u32 val = 0, send_pct, shift;
|
|
|
|
struct cpsw_slave *slave;
|
|
|
|
int pct = 0, i;
|
|
|
|
|
|
|
|
if (bw > priv->shp_cfg_speed * 1000)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
/* shaping has to stay enabled for highest fifos linearly
|
|
|
|
* and fifo bw no more then interface can allow
|
|
|
|
*/
|
|
|
|
slave = &cpsw->slaves[cpsw_slave_index(cpsw, priv)];
|
|
|
|
send_pct = slave_read(slave, SEND_PERCENT);
|
|
|
|
for (i = CPSW_FIFO_SHAPERS_NUM; i > 0; i--) {
|
|
|
|
if (!bw) {
|
|
|
|
if (i >= fifo || !priv->fifo_bw[i])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
dev_warn(priv->dev, "Prev FIFO%d is shaped", i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!priv->fifo_bw[i] && i > fifo) {
|
|
|
|
dev_err(priv->dev, "Upper FIFO%d is not shaped", i);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
shift = (i - 1) * 8;
|
|
|
|
if (i == fifo) {
|
|
|
|
send_pct &= ~(CPSW_PCT_MASK << shift);
|
|
|
|
val = DIV_ROUND_UP(bw, priv->shp_cfg_speed * 10);
|
|
|
|
if (!val)
|
|
|
|
val = 1;
|
|
|
|
|
|
|
|
send_pct |= val << shift;
|
|
|
|
pct += val;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (priv->fifo_bw[i])
|
|
|
|
pct += (send_pct >> shift) & CPSW_PCT_MASK;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pct >= 100)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
slave_write(slave, send_pct, SEND_PERCENT);
|
|
|
|
priv->fifo_bw[fifo] = bw;
|
|
|
|
|
|
|
|
dev_warn(priv->dev, "set FIFO%d bw = %d\n", fifo,
|
|
|
|
DIV_ROUND_CLOSEST(val * priv->shp_cfg_speed, 100));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
err:
|
|
|
|
dev_err(priv->dev, "Bandwidth doesn't fit in tc configuration");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_set_fifo_rlimit(struct cpsw_priv *priv, int fifo, int bw)
|
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
struct cpsw_slave *slave;
|
|
|
|
u32 tx_in_ctl_rg, val;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = cpsw_set_fifo_bw(priv, fifo, bw);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
slave = &cpsw->slaves[cpsw_slave_index(cpsw, priv)];
|
|
|
|
tx_in_ctl_rg = cpsw->version == CPSW_VERSION_1 ?
|
|
|
|
CPSW1_TX_IN_CTL : CPSW2_TX_IN_CTL;
|
|
|
|
|
|
|
|
if (!bw)
|
|
|
|
cpsw_fifo_shp_on(priv, fifo, bw);
|
|
|
|
|
|
|
|
val = slave_read(slave, tx_in_ctl_rg);
|
|
|
|
if (cpsw_shp_is_off(priv)) {
|
|
|
|
/* disable FIFOs rate limited queues */
|
|
|
|
val &= ~(0xf << CPSW_FIFO_RATE_EN_SHIFT);
|
|
|
|
|
|
|
|
/* set type of FIFO queues to normal priority mode */
|
|
|
|
val &= ~(3 << CPSW_FIFO_QUEUE_TYPE_SHIFT);
|
|
|
|
|
|
|
|
/* set type of FIFO queues to be rate limited */
|
|
|
|
if (bw)
|
|
|
|
val |= 2 << CPSW_FIFO_QUEUE_TYPE_SHIFT;
|
|
|
|
else
|
|
|
|
priv->shp_cfg_speed = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* toggle a FIFO rate limited queue */
|
|
|
|
if (bw)
|
|
|
|
val |= BIT(fifo + CPSW_FIFO_RATE_EN_SHIFT);
|
|
|
|
else
|
|
|
|
val &= ~BIT(fifo + CPSW_FIFO_RATE_EN_SHIFT);
|
|
|
|
slave_write(slave, val, tx_in_ctl_rg);
|
|
|
|
|
|
|
|
/* FIFO transmit shape enable */
|
|
|
|
cpsw_fifo_shp_on(priv, fifo, bw);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Defaults:
|
|
|
|
* class A - prio 3
|
|
|
|
* class B - prio 2
|
|
|
|
* shaping for class A should be set first
|
|
|
|
*/
|
|
|
|
static int cpsw_set_cbs(struct net_device *ndev,
|
|
|
|
struct tc_cbs_qopt_offload *qopt)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
struct cpsw_slave *slave;
|
|
|
|
int prev_speed = 0;
|
|
|
|
int tc, ret, fifo;
|
|
|
|
u32 bw = 0;
|
|
|
|
|
|
|
|
tc = netdev_txq_to_tc(priv->ndev, qopt->queue);
|
|
|
|
|
|
|
|
/* enable channels in backward order, as highest FIFOs must be rate
|
|
|
|
* limited first and for compliance with CPDMA rate limited channels
|
|
|
|
* that also used in bacward order. FIFO0 cannot be rate limited.
|
|
|
|
*/
|
|
|
|
fifo = cpsw_tc_to_fifo(tc, ndev->num_tc);
|
|
|
|
if (!fifo) {
|
|
|
|
dev_err(priv->dev, "Last tc%d can't be rate limited", tc);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* do nothing, it's disabled anyway */
|
|
|
|
if (!qopt->enable && !priv->fifo_bw[fifo])
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* shapers can be set if link speed is known */
|
|
|
|
slave = &cpsw->slaves[cpsw_slave_index(cpsw, priv)];
|
|
|
|
if (slave->phy && slave->phy->link) {
|
|
|
|
if (priv->shp_cfg_speed &&
|
|
|
|
priv->shp_cfg_speed != slave->phy->speed)
|
|
|
|
prev_speed = priv->shp_cfg_speed;
|
|
|
|
|
|
|
|
priv->shp_cfg_speed = slave->phy->speed;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!priv->shp_cfg_speed) {
|
|
|
|
dev_err(priv->dev, "Link speed is not known");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pm_runtime_get_sync(cpsw->dev);
|
|
|
|
if (ret < 0) {
|
|
|
|
pm_runtime_put_noidle(cpsw->dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bw = qopt->enable ? qopt->idleslope : 0;
|
|
|
|
ret = cpsw_set_fifo_rlimit(priv, fifo, bw);
|
|
|
|
if (ret) {
|
|
|
|
priv->shp_cfg_speed = prev_speed;
|
|
|
|
prev_speed = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bw && prev_speed)
|
|
|
|
dev_warn(priv->dev,
|
|
|
|
"Speed was changed, CBS shaper speeds are changed!");
|
|
|
|
|
|
|
|
pm_runtime_put_sync(cpsw->dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-07-24 05:26:33 +08:00
|
|
|
static void cpsw_cbs_resume(struct cpsw_slave *slave, struct cpsw_priv *priv)
|
|
|
|
{
|
|
|
|
int fifo, bw;
|
|
|
|
|
|
|
|
for (fifo = CPSW_FIFO_SHAPERS_NUM; fifo > 0; fifo--) {
|
|
|
|
bw = priv->fifo_bw[fifo];
|
|
|
|
if (!bw)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
cpsw_set_fifo_rlimit(priv, fifo, bw);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cpsw_mqprio_resume(struct cpsw_slave *slave, struct cpsw_priv *priv)
|
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
u32 tx_prio_map = 0;
|
|
|
|
int i, tc, fifo;
|
|
|
|
u32 tx_prio_rg;
|
|
|
|
|
|
|
|
if (!priv->mqprio_hw)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
|
|
tc = netdev_get_prio_tc_map(priv->ndev, i);
|
|
|
|
fifo = CPSW_FIFO_SHAPERS_NUM - tc;
|
|
|
|
tx_prio_map |= fifo << (4 * i);
|
|
|
|
}
|
|
|
|
|
|
|
|
tx_prio_rg = cpsw->version == CPSW_VERSION_1 ?
|
|
|
|
CPSW1_TX_PRI_MAP : CPSW2_TX_PRI_MAP;
|
|
|
|
|
|
|
|
slave_write(slave, tx_prio_map, tx_prio_rg);
|
|
|
|
}
|
|
|
|
|
2018-11-09 04:27:57 +08:00
|
|
|
static int cpsw_restore_vlans(struct net_device *vdev, int vid, void *arg)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = arg;
|
|
|
|
|
|
|
|
if (!vdev)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
cpsw_ndo_vlan_rx_add_vid(priv->ndev, 0, vid);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-24 05:26:33 +08:00
|
|
|
/* restore resources after port reset */
|
|
|
|
static void cpsw_restore(struct cpsw_priv *priv)
|
|
|
|
{
|
2018-11-09 04:27:57 +08:00
|
|
|
/* restore vlan configurations */
|
|
|
|
vlan_for_each(priv->ndev, cpsw_restore_vlans, priv);
|
|
|
|
|
2018-07-24 05:26:33 +08:00
|
|
|
/* restore MQPRIO offload */
|
|
|
|
for_each_slave(priv, cpsw_mqprio_resume, priv);
|
|
|
|
|
|
|
|
/* restore CBS offload */
|
|
|
|
for_each_slave(priv, cpsw_cbs_resume, priv);
|
|
|
|
}
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
static int cpsw_ndo_open(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
2016-08-10 07:22:37 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2016-08-23 02:18:24 +08:00
|
|
|
int ret;
|
2012-03-19 04:17:54 +08:00
|
|
|
u32 reg;
|
|
|
|
|
2016-08-10 07:22:38 +08:00
|
|
|
ret = pm_runtime_get_sync(cpsw->dev);
|
2016-06-25 02:23:42 +08:00
|
|
|
if (ret < 0) {
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put_noidle(cpsw->dev);
|
2016-06-25 02:23:42 +08:00
|
|
|
return ret;
|
|
|
|
}
|
2016-04-20 02:09:49 +08:00
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
netif_carrier_off(ndev);
|
|
|
|
|
2016-08-23 02:18:26 +08:00
|
|
|
/* Notify the stack of the actual queue counts. */
|
|
|
|
ret = netif_set_real_num_tx_queues(ndev, cpsw->tx_ch_num);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(priv->dev, "cannot set real number of tx queues\n");
|
|
|
|
goto err_cleanup;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = netif_set_real_num_rx_queues(ndev, cpsw->rx_ch_num);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(priv->dev, "cannot set real number of rx queues\n");
|
|
|
|
goto err_cleanup;
|
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
reg = cpsw->version;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
dev_info(priv->dev, "initializing cpsw version %d.%d (%d)\n",
|
|
|
|
CPSW_MAJOR_VERSION(reg), CPSW_MINOR_VERSION(reg),
|
|
|
|
CPSW_RTL_VERSION(reg));
|
|
|
|
|
2017-02-14 22:02:36 +08:00
|
|
|
/* Initialize host and slave ports */
|
|
|
|
if (!cpsw->usage_count)
|
2013-02-11 17:52:20 +08:00
|
|
|
cpsw_init_host_port(priv);
|
2012-03-19 04:17:54 +08:00
|
|
|
for_each_slave(priv, cpsw_slave_open, priv);
|
|
|
|
|
2013-02-05 16:26:48 +08:00
|
|
|
/* Add default VLAN */
|
2016-08-10 07:22:42 +08:00
|
|
|
if (!cpsw->data.dual_emac)
|
2014-06-18 19:51:48 +08:00
|
|
|
cpsw_add_default_vlan(priv);
|
|
|
|
else
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_vlan(cpsw->ale, cpsw->data.default_vlan,
|
drivers: net: cpsw: fix port_mask parameters in ale calls
ALE APIs expect to receive port masks as input values for arguments
port_mask, untag, reg_mcast, unreg_mcast. But there are few places in
code where port masks are passed left-shifted by cpsw_priv->host_port,
like below:
cpsw_ale_add_vlan(priv->ale, priv->data.default_vlan,
ALE_ALL_PORTS << priv->host_port,
ALE_ALL_PORTS << priv->host_port, 0, 0);
and cpsw is still working just because priv->host_port == 0
and has never ever been changed.
Hence, fix port_mask parameters in ALE APIs calls and drop
"<< priv->host_port" from all places where it's used to
shift valid port mask.
Signed-off-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-04-07 20:16:43 +08:00
|
|
|
ALE_ALL_PORTS, ALE_ALL_PORTS, 0, 0);
|
2013-02-05 16:26:48 +08:00
|
|
|
|
2017-02-14 22:02:36 +08:00
|
|
|
/* initialize shared resources for every ndev */
|
|
|
|
if (!cpsw->usage_count) {
|
2013-02-11 17:52:20 +08:00
|
|
|
/* disable priority elevation */
|
2017-12-01 08:21:11 +08:00
|
|
|
writel_relaxed(0, &cpsw->regs->ptype);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2013-02-11 17:52:20 +08:00
|
|
|
/* enable statistics collection only on all ports */
|
2017-12-01 08:21:11 +08:00
|
|
|
writel_relaxed(0x7, &cpsw->regs->stat_port_en);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2014-09-09 01:24:02 +08:00
|
|
|
/* Enable internal fifo flow control */
|
2016-08-10 07:22:39 +08:00
|
|
|
writel(0x7, &cpsw->regs->flow_control);
|
2014-09-09 01:24:02 +08:00
|
|
|
|
2016-08-10 07:22:43 +08:00
|
|
|
napi_enable(&cpsw->napi_rx);
|
|
|
|
napi_enable(&cpsw->napi_tx);
|
2015-08-04 18:36:19 +08:00
|
|
|
|
2016-08-10 07:22:41 +08:00
|
|
|
if (cpsw->tx_irq_disabled) {
|
|
|
|
cpsw->tx_irq_disabled = false;
|
|
|
|
enable_irq(cpsw->irqs_table[1]);
|
2015-08-12 17:52:53 +08:00
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:41 +08:00
|
|
|
if (cpsw->rx_irq_disabled) {
|
|
|
|
cpsw->rx_irq_disabled = false;
|
|
|
|
enable_irq(cpsw->irqs_table[0]);
|
2015-08-12 17:52:53 +08:00
|
|
|
}
|
|
|
|
|
2016-08-23 02:18:24 +08:00
|
|
|
ret = cpsw_fill_rx_channels(priv);
|
|
|
|
if (ret < 0)
|
|
|
|
goto err_cleanup;
|
2013-12-12 12:09:05 +08:00
|
|
|
|
2016-12-07 08:00:41 +08:00
|
|
|
if (cpts_register(cpsw->cpts))
|
2013-12-12 12:09:05 +08:00
|
|
|
dev_err(priv->dev, "error registering cpts device\n");
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2018-07-24 05:26:33 +08:00
|
|
|
cpsw_restore(priv);
|
|
|
|
|
2013-03-12 07:16:37 +08:00
|
|
|
/* Enable Interrupt pacing if configured */
|
2016-08-10 07:22:44 +08:00
|
|
|
if (cpsw->coal_intvl != 0) {
|
2013-03-12 07:16:37 +08:00
|
|
|
struct ethtool_coalesce coal;
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
coal.rx_coalesce_usecs = cpsw->coal_intvl;
|
2013-03-12 07:16:37 +08:00
|
|
|
cpsw_set_coalesce(ndev, &coal);
|
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:40 +08:00
|
|
|
cpdma_ctlr_start(cpsw->dma);
|
|
|
|
cpsw_intr_enable(cpsw);
|
2017-02-14 22:02:36 +08:00
|
|
|
cpsw->usage_count++;
|
2014-04-10 16:53:24 +08:00
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
return 0;
|
|
|
|
|
2013-04-23 15:31:36 +08:00
|
|
|
err_cleanup:
|
2016-08-10 07:22:40 +08:00
|
|
|
cpdma_ctlr_stop(cpsw->dma);
|
2016-08-10 07:22:44 +08:00
|
|
|
for_each_slave(priv, cpsw_slave_stop, cpsw);
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put_sync(cpsw->dev);
|
2013-04-23 15:31:36 +08:00
|
|
|
netif_carrier_off(priv->ndev);
|
|
|
|
return ret;
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_ndo_stop(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
2016-08-10 07:22:37 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
cpsw_info(priv, ifdown, "shutting down cpsw device\n");
|
2018-11-09 04:27:56 +08:00
|
|
|
__hw_addr_ref_unsync_dev(&ndev->mc, ndev, cpsw_purge_all_mc);
|
2016-08-23 02:18:26 +08:00
|
|
|
netif_tx_stop_all_queues(priv->ndev);
|
2012-03-19 04:17:54 +08:00
|
|
|
netif_carrier_off(priv->ndev);
|
2013-02-11 17:52:20 +08:00
|
|
|
|
2017-02-14 22:02:36 +08:00
|
|
|
if (cpsw->usage_count <= 1) {
|
2016-08-10 07:22:43 +08:00
|
|
|
napi_disable(&cpsw->napi_rx);
|
|
|
|
napi_disable(&cpsw->napi_tx);
|
2016-08-10 07:22:44 +08:00
|
|
|
cpts_unregister(cpsw->cpts);
|
2016-08-10 07:22:40 +08:00
|
|
|
cpsw_intr_disable(cpsw);
|
|
|
|
cpdma_ctlr_stop(cpsw->dma);
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_stop(cpsw->ale);
|
2013-02-11 17:52:20 +08:00
|
|
|
}
|
2016-08-10 07:22:44 +08:00
|
|
|
for_each_slave(priv, cpsw_slave_stop, cpsw);
|
2016-12-10 20:23:49 +08:00
|
|
|
|
|
|
|
if (cpsw_need_resplit(cpsw))
|
2019-04-27 01:12:26 +08:00
|
|
|
cpsw_split_res(cpsw);
|
2016-12-10 20:23:49 +08:00
|
|
|
|
2017-02-14 22:02:36 +08:00
|
|
|
cpsw->usage_count--;
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put_sync(cpsw->dev);
|
2012-03-19 04:17:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static netdev_tx_t cpsw_ndo_start_xmit(struct sk_buff *skb,
|
|
|
|
struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
2016-08-10 07:22:40 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2017-06-27 21:58:52 +08:00
|
|
|
struct cpts *cpts = cpsw->cpts;
|
2016-08-23 02:18:26 +08:00
|
|
|
struct netdev_queue *txq;
|
|
|
|
struct cpdma_chan *txch;
|
|
|
|
int ret, q_idx;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
if (skb_padto(skb, CPSW_MIN_PACKET_SIZE)) {
|
|
|
|
cpsw_err(priv, tx_err, "packet pad failed\n");
|
2014-03-10 20:12:23 +08:00
|
|
|
ndev->stats.tx_dropped++;
|
2017-02-11 09:49:57 +08:00
|
|
|
return NET_XMIT_DROP;
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2013-02-11 17:52:19 +08:00
|
|
|
if (skb_shinfo(skb)->tx_flags & SKBTX_HW_TSTAMP &&
|
2018-11-12 22:00:22 +08:00
|
|
|
priv->tx_ts_enabled && cpts_can_timestamp(cpts, skb))
|
2012-10-29 16:45:20 +08:00
|
|
|
skb_shinfo(skb)->tx_flags |= SKBTX_IN_PROGRESS;
|
|
|
|
|
2016-08-23 02:18:26 +08:00
|
|
|
q_idx = skb_get_queue_mapping(skb);
|
|
|
|
if (q_idx >= cpsw->tx_ch_num)
|
|
|
|
q_idx = q_idx % cpsw->tx_ch_num;
|
|
|
|
|
2016-11-29 23:00:51 +08:00
|
|
|
txch = cpsw->txv[q_idx].ch;
|
2018-02-07 09:17:06 +08:00
|
|
|
txq = netdev_get_tx_queue(ndev, q_idx);
|
2019-04-27 01:12:30 +08:00
|
|
|
skb_tx_timestamp(skb);
|
|
|
|
ret = cpdma_chan_submit(txch, skb, skb->data, skb->len,
|
|
|
|
priv->emac_port + cpsw->data.dual_emac);
|
2012-03-19 04:17:54 +08:00
|
|
|
if (unlikely(ret != 0)) {
|
|
|
|
cpsw_err(priv, tx_err, "desc submit failed\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2013-01-17 14:31:34 +08:00
|
|
|
/* If there is no more tx desc left free then we need to
|
|
|
|
* tell the kernel to stop sending us tx frames.
|
|
|
|
*/
|
2016-08-23 02:18:26 +08:00
|
|
|
if (unlikely(!cpdma_check_free_tx_desc(txch))) {
|
|
|
|
netif_tx_stop_queue(txq);
|
2018-02-07 09:17:06 +08:00
|
|
|
|
|
|
|
/* Barrier, so that stop_queue visible to other cpus */
|
|
|
|
smp_mb__after_atomic();
|
|
|
|
|
|
|
|
if (cpdma_check_free_tx_desc(txch))
|
|
|
|
netif_tx_wake_queue(txq);
|
2016-08-23 02:18:26 +08:00
|
|
|
}
|
2013-01-17 14:31:34 +08:00
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
return NETDEV_TX_OK;
|
|
|
|
fail:
|
2014-03-10 20:12:23 +08:00
|
|
|
ndev->stats.tx_dropped++;
|
2016-08-23 02:18:26 +08:00
|
|
|
netif_tx_stop_queue(txq);
|
2018-02-07 09:17:06 +08:00
|
|
|
|
|
|
|
/* Barrier, so that stop_queue visible to other cpus */
|
|
|
|
smp_mb__after_atomic();
|
|
|
|
|
|
|
|
if (cpdma_check_free_tx_desc(txch))
|
|
|
|
netif_tx_wake_queue(txq);
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
return NETDEV_TX_BUSY;
|
|
|
|
}
|
|
|
|
|
2016-12-07 08:00:34 +08:00
|
|
|
#if IS_ENABLED(CONFIG_TI_CPTS)
|
2012-10-29 16:45:20 +08:00
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
static void cpsw_hwtstamp_v1(struct cpsw_priv *priv)
|
2012-10-29 16:45:20 +08:00
|
|
|
{
|
2018-11-12 22:00:22 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2016-08-10 07:22:42 +08:00
|
|
|
struct cpsw_slave *slave = &cpsw->slaves[cpsw->data.active_slave];
|
2012-10-29 16:45:20 +08:00
|
|
|
u32 ts_en, seq_id;
|
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
if (!priv->tx_ts_enabled && !priv->rx_ts_enabled) {
|
2012-10-29 16:45:20 +08:00
|
|
|
slave_write(slave, 0, CPSW1_TS_CTL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
seq_id = (30 << CPSW_V1_SEQ_ID_OFS_SHIFT) | ETH_P_1588;
|
|
|
|
ts_en = EVENT_MSG_BITS << CPSW_V1_MSG_TYPE_OFS;
|
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
if (priv->tx_ts_enabled)
|
2012-10-29 16:45:20 +08:00
|
|
|
ts_en |= CPSW_V1_TS_TX_EN;
|
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
if (priv->rx_ts_enabled)
|
2012-10-29 16:45:20 +08:00
|
|
|
ts_en |= CPSW_V1_TS_RX_EN;
|
|
|
|
|
|
|
|
slave_write(slave, ts_en, CPSW1_TS_CTL);
|
|
|
|
slave_write(slave, seq_id, CPSW1_TS_SEQ_LTYPE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cpsw_hwtstamp_v2(struct cpsw_priv *priv)
|
|
|
|
{
|
2013-02-11 17:52:20 +08:00
|
|
|
struct cpsw_slave *slave;
|
2016-08-10 07:22:39 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2012-10-29 16:45:20 +08:00
|
|
|
u32 ctrl, mtype;
|
|
|
|
|
2016-12-10 20:23:46 +08:00
|
|
|
slave = &cpsw->slaves[cpsw_slave_index(cpsw, priv)];
|
2013-02-11 17:52:20 +08:00
|
|
|
|
2012-10-29 16:45:20 +08:00
|
|
|
ctrl = slave_read(slave, CPSW2_CONTROL);
|
2016-08-10 07:22:44 +08:00
|
|
|
switch (cpsw->version) {
|
2014-05-02 14:32:02 +08:00
|
|
|
case CPSW_VERSION_2:
|
|
|
|
ctrl &= ~CTRL_V2_ALL_TS_MASK;
|
2012-10-29 16:45:20 +08:00
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
if (priv->tx_ts_enabled)
|
2014-05-02 14:32:02 +08:00
|
|
|
ctrl |= CTRL_V2_TX_TS_BITS;
|
2012-10-29 16:45:20 +08:00
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
if (priv->rx_ts_enabled)
|
2014-05-02 14:32:02 +08:00
|
|
|
ctrl |= CTRL_V2_RX_TS_BITS;
|
2015-05-25 17:02:13 +08:00
|
|
|
break;
|
2014-05-02 14:32:02 +08:00
|
|
|
case CPSW_VERSION_3:
|
|
|
|
default:
|
|
|
|
ctrl &= ~CTRL_V3_ALL_TS_MASK;
|
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
if (priv->tx_ts_enabled)
|
2014-05-02 14:32:02 +08:00
|
|
|
ctrl |= CTRL_V3_TX_TS_BITS;
|
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
if (priv->rx_ts_enabled)
|
2014-05-02 14:32:02 +08:00
|
|
|
ctrl |= CTRL_V3_RX_TS_BITS;
|
2015-05-25 17:02:13 +08:00
|
|
|
break;
|
2014-05-02 14:32:02 +08:00
|
|
|
}
|
2012-10-29 16:45:20 +08:00
|
|
|
|
|
|
|
mtype = (30 << TS_SEQ_ID_OFFSET_SHIFT) | EVENT_MSG_BITS;
|
|
|
|
|
|
|
|
slave_write(slave, mtype, CPSW2_TS_SEQ_MTYPE);
|
|
|
|
slave_write(slave, ctrl, CPSW2_CONTROL);
|
2017-12-01 08:21:11 +08:00
|
|
|
writel_relaxed(ETH_P_1588, &cpsw->regs->ts_ltype);
|
2018-11-12 22:00:23 +08:00
|
|
|
writel_relaxed(ETH_P_8021Q, &cpsw->regs->vlan_ltype);
|
2012-10-29 16:45:20 +08:00
|
|
|
}
|
|
|
|
|
2013-11-19 07:23:40 +08:00
|
|
|
static int cpsw_hwtstamp_set(struct net_device *dev, struct ifreq *ifr)
|
2012-10-29 16:45:20 +08:00
|
|
|
{
|
2012-11-27 15:53:40 +08:00
|
|
|
struct cpsw_priv *priv = netdev_priv(dev);
|
2012-10-29 16:45:20 +08:00
|
|
|
struct hwtstamp_config cfg;
|
2016-08-10 07:22:44 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2012-10-29 16:45:20 +08:00
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
if (cpsw->version != CPSW_VERSION_1 &&
|
|
|
|
cpsw->version != CPSW_VERSION_2 &&
|
|
|
|
cpsw->version != CPSW_VERSION_3)
|
2013-11-14 08:47:36 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
2012-10-29 16:45:20 +08:00
|
|
|
if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
|
|
|
|
return -EFAULT;
|
|
|
|
|
|
|
|
/* reserved for future extensions */
|
|
|
|
if (cfg.flags)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2013-11-14 08:47:36 +08:00
|
|
|
if (cfg.tx_type != HWTSTAMP_TX_OFF && cfg.tx_type != HWTSTAMP_TX_ON)
|
2012-10-29 16:45:20 +08:00
|
|
|
return -ERANGE;
|
|
|
|
|
|
|
|
switch (cfg.rx_filter) {
|
|
|
|
case HWTSTAMP_FILTER_NONE:
|
2018-11-12 22:00:22 +08:00
|
|
|
priv->rx_ts_enabled = 0;
|
2012-10-29 16:45:20 +08:00
|
|
|
break;
|
|
|
|
case HWTSTAMP_FILTER_ALL:
|
2017-06-09 02:51:31 +08:00
|
|
|
case HWTSTAMP_FILTER_NTP_ALL:
|
|
|
|
return -ERANGE;
|
2012-10-29 16:45:20 +08:00
|
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
|
2018-11-12 22:00:22 +08:00
|
|
|
priv->rx_ts_enabled = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
|
2017-06-09 02:51:31 +08:00
|
|
|
cfg.rx_filter = HWTSTAMP_FILTER_PTP_V1_L4_EVENT;
|
|
|
|
break;
|
2012-10-29 16:45:20 +08:00
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_EVENT:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_SYNC:
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
|
2018-11-12 22:00:22 +08:00
|
|
|
priv->rx_ts_enabled = HWTSTAMP_FILTER_PTP_V2_EVENT;
|
2012-10-29 16:45:20 +08:00
|
|
|
cfg.rx_filter = HWTSTAMP_FILTER_PTP_V2_EVENT;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return -ERANGE;
|
|
|
|
}
|
|
|
|
|
2018-11-12 22:00:22 +08:00
|
|
|
priv->tx_ts_enabled = cfg.tx_type == HWTSTAMP_TX_ON;
|
2013-11-14 08:47:36 +08:00
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
switch (cpsw->version) {
|
2012-10-29 16:45:20 +08:00
|
|
|
case CPSW_VERSION_1:
|
2018-11-12 22:00:22 +08:00
|
|
|
cpsw_hwtstamp_v1(priv);
|
2012-10-29 16:45:20 +08:00
|
|
|
break;
|
|
|
|
case CPSW_VERSION_2:
|
2014-05-02 14:32:01 +08:00
|
|
|
case CPSW_VERSION_3:
|
2012-10-29 16:45:20 +08:00
|
|
|
cpsw_hwtstamp_v2(priv);
|
|
|
|
break;
|
|
|
|
default:
|
2013-11-14 08:47:36 +08:00
|
|
|
WARN_ON(1);
|
2012-10-29 16:45:20 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
|
|
|
|
}
|
|
|
|
|
2013-11-19 07:23:40 +08:00
|
|
|
static int cpsw_hwtstamp_get(struct net_device *dev, struct ifreq *ifr)
|
|
|
|
{
|
2016-08-10 07:22:44 +08:00
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(dev);
|
2018-11-12 22:00:22 +08:00
|
|
|
struct cpsw_priv *priv = netdev_priv(dev);
|
2013-11-19 07:23:40 +08:00
|
|
|
struct hwtstamp_config cfg;
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
if (cpsw->version != CPSW_VERSION_1 &&
|
|
|
|
cpsw->version != CPSW_VERSION_2 &&
|
|
|
|
cpsw->version != CPSW_VERSION_3)
|
2013-11-19 07:23:40 +08:00
|
|
|
return -EOPNOTSUPP;
|
|
|
|
|
|
|
|
cfg.flags = 0;
|
2018-11-12 22:00:22 +08:00
|
|
|
cfg.tx_type = priv->tx_ts_enabled ? HWTSTAMP_TX_ON : HWTSTAMP_TX_OFF;
|
|
|
|
cfg.rx_filter = priv->rx_ts_enabled;
|
2013-11-19 07:23:40 +08:00
|
|
|
|
|
|
|
return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
|
|
|
|
}
|
2016-12-07 08:00:34 +08:00
|
|
|
#else
|
|
|
|
static int cpsw_hwtstamp_get(struct net_device *dev, struct ifreq *ifr)
|
|
|
|
{
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
2013-11-19 07:23:40 +08:00
|
|
|
|
2016-12-07 08:00:34 +08:00
|
|
|
static int cpsw_hwtstamp_set(struct net_device *dev, struct ifreq *ifr)
|
|
|
|
{
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
2012-10-29 16:45:20 +08:00
|
|
|
#endif /*CONFIG_TI_CPTS*/
|
|
|
|
|
|
|
|
static int cpsw_ndo_ioctl(struct net_device *dev, struct ifreq *req, int cmd)
|
|
|
|
{
|
2013-03-12 07:16:38 +08:00
|
|
|
struct cpsw_priv *priv = netdev_priv(dev);
|
2016-08-10 07:22:42 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
int slave_no = cpsw_slave_index(cpsw, priv);
|
2013-03-12 07:16:38 +08:00
|
|
|
|
2012-10-29 16:45:20 +08:00
|
|
|
if (!netif_running(dev))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2013-03-12 07:16:38 +08:00
|
|
|
switch (cmd) {
|
|
|
|
case SIOCSHWTSTAMP:
|
2013-11-19 07:23:40 +08:00
|
|
|
return cpsw_hwtstamp_set(dev, req);
|
|
|
|
case SIOCGHWTSTAMP:
|
|
|
|
return cpsw_hwtstamp_get(dev, req);
|
2013-03-12 07:16:38 +08:00
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (!cpsw->slaves[slave_no].phy)
|
2014-02-16 21:54:25 +08:00
|
|
|
return -EOPNOTSUPP;
|
2016-08-10 07:22:42 +08:00
|
|
|
return phy_mii_ioctl(cpsw->slaves[slave_no].phy, req, cmd);
|
2012-10-29 16:45:20 +08:00
|
|
|
}
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
static void cpsw_ndo_tx_timeout(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
2016-08-10 07:22:40 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2016-08-23 02:18:26 +08:00
|
|
|
int ch;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
cpsw_err(priv, tx_err, "transmit timeout, restarting dma\n");
|
2014-03-10 20:12:23 +08:00
|
|
|
ndev->stats.tx_errors++;
|
2016-08-10 07:22:40 +08:00
|
|
|
cpsw_intr_disable(cpsw);
|
2016-08-23 02:18:26 +08:00
|
|
|
for (ch = 0; ch < cpsw->tx_ch_num; ch++) {
|
2016-11-29 23:00:51 +08:00
|
|
|
cpdma_chan_stop(cpsw->txv[ch].ch);
|
|
|
|
cpdma_chan_start(cpsw->txv[ch].ch);
|
2016-08-23 02:18:26 +08:00
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:40 +08:00
|
|
|
cpsw_intr_enable(cpsw);
|
2017-04-01 07:41:23 +08:00
|
|
|
netif_trans_update(ndev);
|
|
|
|
netif_tx_wake_all_queues(ndev);
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2013-07-26 02:14:01 +08:00
|
|
|
static int cpsw_ndo_set_mac_address(struct net_device *ndev, void *p)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
|
|
|
struct sockaddr *addr = (struct sockaddr *)p;
|
2016-08-10 07:22:37 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2013-07-26 02:14:01 +08:00
|
|
|
int flags = 0;
|
|
|
|
u16 vid = 0;
|
2016-06-25 02:23:45 +08:00
|
|
|
int ret;
|
2013-07-26 02:14:01 +08:00
|
|
|
|
|
|
|
if (!is_valid_ether_addr(addr->sa_data))
|
|
|
|
return -EADDRNOTAVAIL;
|
|
|
|
|
2016-08-10 07:22:38 +08:00
|
|
|
ret = pm_runtime_get_sync(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
if (ret < 0) {
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put_noidle(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac) {
|
|
|
|
vid = cpsw->slaves[priv->emac_port].port_vlan;
|
2013-07-26 02:14:01 +08:00
|
|
|
flags = ALE_VLAN;
|
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr, HOST_PORT_NUM,
|
2013-07-26 02:14:01 +08:00
|
|
|
flags, vid);
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_add_ucast(cpsw->ale, addr->sa_data, HOST_PORT_NUM,
|
2013-07-26 02:14:01 +08:00
|
|
|
flags, vid);
|
|
|
|
|
|
|
|
memcpy(priv->mac_addr, addr->sa_data, ETH_ALEN);
|
|
|
|
memcpy(ndev->dev_addr, priv->mac_addr, ETH_ALEN);
|
|
|
|
for_each_slave(priv, cpsw_set_slave_mac, priv);
|
|
|
|
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
|
2013-07-26 02:14:01 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-02-05 16:26:48 +08:00
|
|
|
static inline int cpsw_add_vlan_ale_entry(struct cpsw_priv *priv,
|
|
|
|
unsigned short vid)
|
|
|
|
{
|
|
|
|
int ret;
|
2015-01-15 17:29:28 +08:00
|
|
|
int unreg_mcast_mask = 0;
|
net: ethernet: ti: cpsw: use for mcast entries only host port
In dual-emac mode the cpsw driver sends directed packets, that means
that packets go to the directed port, but an ALE lookup is performed
to determine untagged egress only. It means that on tx side no need
to add port bit for ALE mcast entry mask, and basically ALE entry
for port identification is needed only on rx side.
So, add only host port in dual_emac mode as used directed
transmission, and no need in one more port. For single port boards
and switch mode all ports used, as usual, so no changes for them.
Also it simplifies farther changes.
In other words, mcast entries for dual-emac should behave exactly
like unicast. It also can help avoid leaking packets between ports
with same vlan on h/w level if ports could became members of same vid.
So now, for instance, if mcast address 33:33:00:00:00:01 is added then
entries in ALE table:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
Instead of:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x3
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x5
With the same considerations, set only host port for unregistered
mcast for dual-emac mode in case of IFF_ALLMULTI is set, exactly like
it's done in cpsw_ale_set_allmulti().
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-10-13 00:06:29 +08:00
|
|
|
int mcast_mask;
|
2015-01-15 17:29:28 +08:00
|
|
|
u32 port_mask;
|
2016-08-10 07:22:42 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2014-11-01 01:38:52 +08:00
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac) {
|
2015-01-15 17:29:28 +08:00
|
|
|
port_mask = (1 << (priv->emac_port + 1)) | ALE_PORT_HOST;
|
2013-02-05 16:26:48 +08:00
|
|
|
|
net: ethernet: ti: cpsw: use for mcast entries only host port
In dual-emac mode the cpsw driver sends directed packets, that means
that packets go to the directed port, but an ALE lookup is performed
to determine untagged egress only. It means that on tx side no need
to add port bit for ALE mcast entry mask, and basically ALE entry
for port identification is needed only on rx side.
So, add only host port in dual_emac mode as used directed
transmission, and no need in one more port. For single port boards
and switch mode all ports used, as usual, so no changes for them.
Also it simplifies farther changes.
In other words, mcast entries for dual-emac should behave exactly
like unicast. It also can help avoid leaking packets between ports
with same vlan on h/w level if ports could became members of same vid.
So now, for instance, if mcast address 33:33:00:00:00:01 is added then
entries in ALE table:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
Instead of:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x3
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x5
With the same considerations, set only host port for unregistered
mcast for dual-emac mode in case of IFF_ALLMULTI is set, exactly like
it's done in cpsw_ale_set_allmulti().
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-10-13 00:06:29 +08:00
|
|
|
mcast_mask = ALE_PORT_HOST;
|
2015-01-15 17:29:28 +08:00
|
|
|
if (priv->ndev->flags & IFF_ALLMULTI)
|
net: ethernet: ti: cpsw: use for mcast entries only host port
In dual-emac mode the cpsw driver sends directed packets, that means
that packets go to the directed port, but an ALE lookup is performed
to determine untagged egress only. It means that on tx side no need
to add port bit for ALE mcast entry mask, and basically ALE entry
for port identification is needed only on rx side.
So, add only host port in dual_emac mode as used directed
transmission, and no need in one more port. For single port boards
and switch mode all ports used, as usual, so no changes for them.
Also it simplifies farther changes.
In other words, mcast entries for dual-emac should behave exactly
like unicast. It also can help avoid leaking packets between ports
with same vlan on h/w level if ports could became members of same vid.
So now, for instance, if mcast address 33:33:00:00:00:01 is added then
entries in ALE table:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
Instead of:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x3
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x5
With the same considerations, set only host port for unregistered
mcast for dual-emac mode in case of IFF_ALLMULTI is set, exactly like
it's done in cpsw_ale_set_allmulti().
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-10-13 00:06:29 +08:00
|
|
|
unreg_mcast_mask = mcast_mask;
|
2015-01-15 17:29:28 +08:00
|
|
|
} else {
|
|
|
|
port_mask = ALE_ALL_PORTS;
|
net: ethernet: ti: cpsw: use for mcast entries only host port
In dual-emac mode the cpsw driver sends directed packets, that means
that packets go to the directed port, but an ALE lookup is performed
to determine untagged egress only. It means that on tx side no need
to add port bit for ALE mcast entry mask, and basically ALE entry
for port identification is needed only on rx side.
So, add only host port in dual_emac mode as used directed
transmission, and no need in one more port. For single port boards
and switch mode all ports used, as usual, so no changes for them.
Also it simplifies farther changes.
In other words, mcast entries for dual-emac should behave exactly
like unicast. It also can help avoid leaking packets between ports
with same vlan on h/w level if ports could became members of same vid.
So now, for instance, if mcast address 33:33:00:00:00:01 is added then
entries in ALE table:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
Instead of:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x3
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x5
With the same considerations, set only host port for unregistered
mcast for dual-emac mode in case of IFF_ALLMULTI is set, exactly like
it's done in cpsw_ale_set_allmulti().
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-10-13 00:06:29 +08:00
|
|
|
mcast_mask = port_mask;
|
2015-01-15 17:29:28 +08:00
|
|
|
|
|
|
|
if (priv->ndev->flags & IFF_ALLMULTI)
|
|
|
|
unreg_mcast_mask = ALE_ALL_PORTS;
|
|
|
|
else
|
|
|
|
unreg_mcast_mask = ALE_PORT_1 | ALE_PORT_2;
|
|
|
|
}
|
2013-02-05 16:26:48 +08:00
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
ret = cpsw_ale_add_vlan(cpsw->ale, vid, port_mask, 0, port_mask,
|
drivers: net: cpsw: fix port_mask parameters in ale calls
ALE APIs expect to receive port masks as input values for arguments
port_mask, untag, reg_mcast, unreg_mcast. But there are few places in
code where port masks are passed left-shifted by cpsw_priv->host_port,
like below:
cpsw_ale_add_vlan(priv->ale, priv->data.default_vlan,
ALE_ALL_PORTS << priv->host_port,
ALE_ALL_PORTS << priv->host_port, 0, 0);
and cpsw is still working just because priv->host_port == 0
and has never ever been changed.
Hence, fix port_mask parameters in ALE APIs calls and drop
"<< priv->host_port" from all places where it's used to
shift valid port mask.
Signed-off-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-04-07 20:16:43 +08:00
|
|
|
unreg_mcast_mask);
|
2013-02-05 16:26:48 +08:00
|
|
|
if (ret != 0)
|
|
|
|
return ret;
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
ret = cpsw_ale_add_ucast(cpsw->ale, priv->mac_addr,
|
2016-04-07 20:16:44 +08:00
|
|
|
HOST_PORT_NUM, ALE_VLAN, vid);
|
2013-02-05 16:26:48 +08:00
|
|
|
if (ret != 0)
|
|
|
|
goto clean_vid;
|
|
|
|
|
2016-08-10 07:22:44 +08:00
|
|
|
ret = cpsw_ale_add_mcast(cpsw->ale, priv->ndev->broadcast,
|
net: ethernet: ti: cpsw: use for mcast entries only host port
In dual-emac mode the cpsw driver sends directed packets, that means
that packets go to the directed port, but an ALE lookup is performed
to determine untagged egress only. It means that on tx side no need
to add port bit for ALE mcast entry mask, and basically ALE entry
for port identification is needed only on rx side.
So, add only host port in dual_emac mode as used directed
transmission, and no need in one more port. For single port boards
and switch mode all ports used, as usual, so no changes for them.
Also it simplifies farther changes.
In other words, mcast entries for dual-emac should behave exactly
like unicast. It also can help avoid leaking packets between ports
with same vlan on h/w level if ports could became members of same vid.
So now, for instance, if mcast address 33:33:00:00:00:01 is added then
entries in ALE table:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x1
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x1
Instead of:
vid = 1, addr = 33:33:00:00:00:01, port_mask = 0x3
vid = 2, addr = 33:33:00:00:00:01, port_mask = 0x5
With the same considerations, set only host port for unregistered
mcast for dual-emac mode in case of IFF_ALLMULTI is set, exactly like
it's done in cpsw_ale_set_allmulti().
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-10-13 00:06:29 +08:00
|
|
|
mcast_mask, ALE_VLAN, vid, 0);
|
2013-02-05 16:26:48 +08:00
|
|
|
if (ret != 0)
|
|
|
|
goto clean_vlan_ucast;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
clean_vlan_ucast:
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr,
|
2016-04-07 20:16:44 +08:00
|
|
|
HOST_PORT_NUM, ALE_VLAN, vid);
|
2013-02-05 16:26:48 +08:00
|
|
|
clean_vid:
|
2016-08-10 07:22:44 +08:00
|
|
|
cpsw_ale_del_vlan(cpsw->ale, vid, 0);
|
2013-02-05 16:26:48 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_ndo_vlan_rx_add_vid(struct net_device *ndev,
|
2013-04-19 10:04:28 +08:00
|
|
|
__be16 proto, u16 vid)
|
2013-02-05 16:26:48 +08:00
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
2016-08-10 07:22:37 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2016-06-25 02:23:45 +08:00
|
|
|
int ret;
|
2013-02-05 16:26:48 +08:00
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (vid == cpsw->data.default_vlan)
|
2013-02-05 16:26:48 +08:00
|
|
|
return 0;
|
|
|
|
|
2016-08-10 07:22:38 +08:00
|
|
|
ret = pm_runtime_get_sync(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
if (ret < 0) {
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put_noidle(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac) {
|
2015-01-22 17:49:22 +08:00
|
|
|
/* In dual EMAC, reserved VLAN id should not be used for
|
|
|
|
* creating VLAN interfaces as this can break the dual
|
|
|
|
* EMAC port separation
|
|
|
|
*/
|
|
|
|
int i;
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
for (i = 0; i < cpsw->data.slaves; i++) {
|
2018-08-10 20:47:09 +08:00
|
|
|
if (vid == cpsw->slaves[i].port_vlan) {
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto err;
|
|
|
|
}
|
2015-01-22 17:49:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-05 16:26:48 +08:00
|
|
|
dev_info(priv->dev, "Adding vlanid %d to vlan filter\n", vid);
|
2016-06-25 02:23:45 +08:00
|
|
|
ret = cpsw_add_vlan_ale_entry(priv, vid);
|
2018-08-10 20:47:09 +08:00
|
|
|
err:
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
return ret;
|
2013-02-05 16:26:48 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_ndo_vlan_rx_kill_vid(struct net_device *ndev,
|
2013-04-19 10:04:28 +08:00
|
|
|
__be16 proto, u16 vid)
|
2013-02-05 16:26:48 +08:00
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
2016-08-10 07:22:37 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2013-02-05 16:26:48 +08:00
|
|
|
int ret;
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (vid == cpsw->data.default_vlan)
|
2013-02-05 16:26:48 +08:00
|
|
|
return 0;
|
|
|
|
|
2016-08-10 07:22:38 +08:00
|
|
|
ret = pm_runtime_get_sync(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
if (ret < 0) {
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put_noidle(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac) {
|
2015-01-22 17:49:22 +08:00
|
|
|
int i;
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
for (i = 0; i < cpsw->data.slaves; i++) {
|
|
|
|
if (vid == cpsw->slaves[i].port_vlan)
|
2018-08-10 20:47:09 +08:00
|
|
|
goto err;
|
2015-01-22 17:49:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-05 16:26:48 +08:00
|
|
|
dev_info(priv->dev, "removing vlanid %d from vlan filter\n", vid);
|
2016-08-10 07:22:44 +08:00
|
|
|
ret = cpsw_ale_del_vlan(cpsw->ale, vid, 0);
|
2018-08-10 20:47:08 +08:00
|
|
|
ret |= cpsw_ale_del_ucast(cpsw->ale, priv->mac_addr,
|
|
|
|
HOST_PORT_NUM, ALE_VLAN, vid);
|
|
|
|
ret |= cpsw_ale_del_mcast(cpsw->ale, priv->ndev->broadcast,
|
|
|
|
0, ALE_VLAN, vid);
|
2018-11-09 04:27:56 +08:00
|
|
|
ret |= cpsw_ale_flush_multicast(cpsw->ale, 0, vid);
|
2018-08-10 20:47:09 +08:00
|
|
|
err:
|
2016-08-10 07:22:38 +08:00
|
|
|
pm_runtime_put(cpsw->dev);
|
2016-06-25 02:23:45 +08:00
|
|
|
return ret;
|
2013-02-05 16:26:48 +08:00
|
|
|
}
|
|
|
|
|
2016-11-29 23:00:49 +08:00
|
|
|
static int cpsw_ndo_set_tx_maxrate(struct net_device *ndev, int queue, u32 rate)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
2016-12-10 20:23:50 +08:00
|
|
|
struct cpsw_slave *slave;
|
2016-12-10 20:23:48 +08:00
|
|
|
u32 min_rate;
|
2016-11-29 23:00:49 +08:00
|
|
|
u32 ch_rate;
|
2016-12-10 20:23:50 +08:00
|
|
|
int i, ret;
|
2016-11-29 23:00:49 +08:00
|
|
|
|
|
|
|
ch_rate = netdev_get_tx_queue(ndev, queue)->tx_maxrate;
|
|
|
|
if (ch_rate == rate)
|
|
|
|
return 0;
|
|
|
|
|
2016-12-10 20:23:48 +08:00
|
|
|
ch_rate = rate * 1000;
|
|
|
|
min_rate = cpdma_chan_get_min_rate(cpsw->dma);
|
|
|
|
if ((ch_rate < min_rate && ch_rate)) {
|
|
|
|
dev_err(priv->dev, "The channel rate cannot be less than %dMbps",
|
|
|
|
min_rate);
|
2016-11-29 23:00:49 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2016-12-10 20:23:49 +08:00
|
|
|
if (rate > cpsw->speed) {
|
2016-12-10 20:23:48 +08:00
|
|
|
dev_err(priv->dev, "The channel rate cannot be more than 2Gbps");
|
2016-11-29 23:00:49 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = pm_runtime_get_sync(cpsw->dev);
|
|
|
|
if (ret < 0) {
|
|
|
|
pm_runtime_put_noidle(cpsw->dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-12-10 20:23:48 +08:00
|
|
|
ret = cpdma_chan_set_rate(cpsw->txv[queue].ch, ch_rate);
|
|
|
|
pm_runtime_put(cpsw->dev);
|
2016-11-29 23:00:49 +08:00
|
|
|
|
2016-12-10 20:23:48 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2016-11-29 23:00:49 +08:00
|
|
|
|
2016-12-10 20:23:50 +08:00
|
|
|
/* update rates for slaves tx queues */
|
|
|
|
for (i = 0; i < cpsw->data.slaves; i++) {
|
|
|
|
slave = &cpsw->slaves[i];
|
|
|
|
if (!slave->ndev)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
netdev_get_tx_queue(slave->ndev, queue)->tx_maxrate = rate;
|
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:26 +08:00
|
|
|
cpsw_split_res(cpsw);
|
2016-11-29 23:00:49 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-07-24 05:26:31 +08:00
|
|
|
static int cpsw_set_mqprio(struct net_device *ndev, void *type_data)
|
|
|
|
{
|
|
|
|
struct tc_mqprio_qopt_offload *mqprio = type_data;
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
int fifo, num_tc, count, offset;
|
|
|
|
struct cpsw_slave *slave;
|
|
|
|
u32 tx_prio_map = 0;
|
|
|
|
int i, tc, ret;
|
|
|
|
|
|
|
|
num_tc = mqprio->qopt.num_tc;
|
|
|
|
if (num_tc > CPSW_TC_NUM)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (mqprio->mode != TC_MQPRIO_MODE_DCB)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
ret = pm_runtime_get_sync(cpsw->dev);
|
|
|
|
if (ret < 0) {
|
|
|
|
pm_runtime_put_noidle(cpsw->dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (num_tc) {
|
|
|
|
for (i = 0; i < 8; i++) {
|
|
|
|
tc = mqprio->qopt.prio_tc_map[i];
|
|
|
|
fifo = cpsw_tc_to_fifo(tc, num_tc);
|
|
|
|
tx_prio_map |= fifo << (4 * i);
|
|
|
|
}
|
|
|
|
|
|
|
|
netdev_set_num_tc(ndev, num_tc);
|
|
|
|
for (i = 0; i < num_tc; i++) {
|
|
|
|
count = mqprio->qopt.count[i];
|
|
|
|
offset = mqprio->qopt.offset[i];
|
|
|
|
netdev_set_tc_queue(ndev, i, count, offset);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!mqprio->qopt.hw) {
|
|
|
|
/* restore default configuration */
|
|
|
|
netdev_reset_tc(ndev);
|
|
|
|
tx_prio_map = TX_PRIORITY_MAPPING;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv->mqprio_hw = mqprio->qopt.hw;
|
|
|
|
|
|
|
|
offset = cpsw->version == CPSW_VERSION_1 ?
|
|
|
|
CPSW1_TX_PRI_MAP : CPSW2_TX_PRI_MAP;
|
|
|
|
|
|
|
|
slave = &cpsw->slaves[cpsw_slave_index(cpsw, priv)];
|
|
|
|
slave_write(slave, tx_prio_map, offset);
|
|
|
|
|
|
|
|
pm_runtime_put_sync(cpsw->dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_ndo_setup_tc(struct net_device *ndev, enum tc_setup_type type,
|
|
|
|
void *type_data)
|
|
|
|
{
|
|
|
|
switch (type) {
|
net: ethernet: ti: cpsw: add CBS Qdisc offload
The cpsw has up to 4 FIFOs per port and upper 3 FIFOs can feed rate
limited queue with shaping. In order to set and enable shaping for
those 3 FIFOs queues the network device with CBS qdisc attached is
needed. The CBS configuration is added for dual-emac/single port mode
only, but potentially can be used in switch mode also, based on
switchdev for instance.
Despite the FIFO shapers can work w/o cpdma level shapers the base
usage must be in combine with cpdma level shapers as described in TRM,
that are set as maximum rates for interface queues with sysfs.
One of the possible configuration with txq shapers and CBS shapers:
Configured with echo RATE >
/sys/class/net/eth0/queues/tx-0/tx_maxrate
/---------------------------------------------------
/
/ cpdma level shapers
+----+ +----+ +----+ +----+ +----+ +----+ +----+ +----+
| c7 | | c6 | | c5 | | c4 | | c3 | | c2 | | c1 | | c0 |
\ / \ / \ / \ / \ / \ / \ / \ /
\ / \ / \ / \ / \ / \ / \ / \ /
\/ \/ \/ \/ \/ \/ \/ \/
+---------|------|------|------|-------------------------------------+
| +----+ | | +---+ |
| | +----+ | | |
| v v v v |
| +----+ +----+ +----+ +----+ p p+----+ +----+ +----+ +----+ |
| | | | | | | | | o o| | | | | | | | |
| | f3 | | f2 | | f1 | | f0 | r CPSW r| f3 | | f2 | | f1 | | f0 | |
| | | | | | | | | t t| | | | | | | | |
| \ / \ / \ / \ / 0 1\ / \ / \ / \ / |
| \ X \ / \ / \ / \ / \ / \ / \ / |
| \/ \ \/ \/ \/ \/ \/ \/ \/ |
+-------\------------------------------------------------------------+
\
\ FIFO shaper, set with CBS offload added in this patch,
\ FIFO0 cannot be rate limited
------------------------------------------------------
CBS shaper configuration is supposed to be used with root MQPRIO Qdisc
offload allowing to add sk_prio->tc->txq maps that direct traffic to
appropriate tx queue and maps L2 priority to FIFO shaper.
The CBS shaper is intended to be used for AVB where L2 priority
(pcp field) is used to differentiate class of traffic. So additionally
vlan needs to be created with appropriate egress sk_prio->l2 prio map.
If CBS has several tx queues assigned to it, the sum of their
bandwidth has not overlap bandwidth set for CBS. It's recomended the
CBS bandwidth to be a little bit more.
The CBS shaper is configured with CBS qdisc offload interface using tc
tool from iproute2 packet.
For instance:
$ tc qdisc replace dev eth0 handle 100: parent root mqprio num_tc 3 \
map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 1
$ tc -g class show dev eth0
+---(100:ffe2) mqprio
| +---(100:3) mqprio
| +---(100:4) mqprio
|
+---(100:ffe1) mqprio
| +---(100:2) mqprio
|
+---(100:ffe0) mqprio
+---(100:1) mqprio
$ tc qdisc add dev eth0 parent 100:1 cbs locredit -1440 \
hicredit 60 sendslope -960000 idleslope 40000 offload 1
$ tc qdisc add dev eth0 parent 100:2 cbs locredit -1470 \
hicredit 62 sendslope -980000 idleslope 20000 offload 1
The above code set CBS shapers for tc0 and tc1, for that txq0 and
txq1 is used. Pay attention, the real set bandwidth can differ a bit
due to discreteness of configuration parameters.
Here parameters like locredit, hicredit and sendslope are ignored
internally and are supposed to be set with assumption that maximum
frame size for frame - 1500.
It's supposed that interface speed is not changed while reconnection,
not always is true, so inform user in case speed of interface was
changed, as it can impact on dependent shapers configuration.
For more examples see Documentation.
Reviewed-by: Ilias Apalodimas <ilias.apalodimas@linaro.org>
Reviewed-by: Grygorii Strashko <grygorii.strashko@ti.com>
Signed-off-by: Ivan Khoronzhuk <ivan.khoronzhuk@linaro.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2018-07-24 05:26:32 +08:00
|
|
|
case TC_SETUP_QDISC_CBS:
|
|
|
|
return cpsw_set_cbs(ndev, type_data);
|
|
|
|
|
2018-07-24 05:26:31 +08:00
|
|
|
case TC_SETUP_QDISC_MQPRIO:
|
|
|
|
return cpsw_set_mqprio(ndev, type_data);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return -EOPNOTSUPP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-28 08:08:25 +08:00
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
static void cpsw_ndo_poll_controller(struct net_device *ndev)
|
|
|
|
{
|
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
|
|
|
|
|
|
|
cpsw_intr_disable(cpsw);
|
|
|
|
cpsw_rx_interrupt(cpsw->irqs_table[0], cpsw);
|
|
|
|
cpsw_tx_interrupt(cpsw->irqs_table[1], cpsw);
|
|
|
|
cpsw_intr_enable(cpsw);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
static const struct net_device_ops cpsw_netdev_ops = {
|
|
|
|
.ndo_open = cpsw_ndo_open,
|
|
|
|
.ndo_stop = cpsw_ndo_stop,
|
|
|
|
.ndo_start_xmit = cpsw_ndo_start_xmit,
|
2013-07-26 02:14:01 +08:00
|
|
|
.ndo_set_mac_address = cpsw_ndo_set_mac_address,
|
2012-10-29 16:45:20 +08:00
|
|
|
.ndo_do_ioctl = cpsw_ndo_ioctl,
|
2012-03-19 04:17:54 +08:00
|
|
|
.ndo_validate_addr = eth_validate_addr,
|
|
|
|
.ndo_tx_timeout = cpsw_ndo_tx_timeout,
|
2012-10-29 16:45:11 +08:00
|
|
|
.ndo_set_rx_mode = cpsw_ndo_set_rx_mode,
|
2016-11-29 23:00:49 +08:00
|
|
|
.ndo_set_tx_maxrate = cpsw_ndo_set_tx_maxrate,
|
2012-03-19 04:17:54 +08:00
|
|
|
#ifdef CONFIG_NET_POLL_CONTROLLER
|
|
|
|
.ndo_poll_controller = cpsw_ndo_poll_controller,
|
|
|
|
#endif
|
2013-02-05 16:26:48 +08:00
|
|
|
.ndo_vlan_rx_add_vid = cpsw_ndo_vlan_rx_add_vid,
|
|
|
|
.ndo_vlan_rx_kill_vid = cpsw_ndo_vlan_rx_kill_vid,
|
2018-07-24 05:26:31 +08:00
|
|
|
.ndo_setup_tc = cpsw_ndo_setup_tc,
|
2012-03-19 04:17:54 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static void cpsw_get_drvinfo(struct net_device *ndev,
|
|
|
|
struct ethtool_drvinfo *info)
|
|
|
|
{
|
2016-08-10 07:22:37 +08:00
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
2016-08-10 07:22:38 +08:00
|
|
|
struct platform_device *pdev = to_platform_device(cpsw->dev);
|
2013-01-06 08:44:26 +08:00
|
|
|
|
2014-07-23 01:55:07 +08:00
|
|
|
strlcpy(info->driver, "cpsw", sizeof(info->driver));
|
2013-01-06 08:44:26 +08:00
|
|
|
strlcpy(info->version, "1.0", sizeof(info->version));
|
2016-08-10 07:22:38 +08:00
|
|
|
strlcpy(info->bus_info, pdev->name, sizeof(info->bus_info));
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2014-09-09 01:24:02 +08:00
|
|
|
static int cpsw_set_pauseparam(struct net_device *ndev,
|
|
|
|
struct ethtool_pauseparam *pause)
|
|
|
|
{
|
|
|
|
struct cpsw_priv *priv = netdev_priv(ndev);
|
|
|
|
bool link;
|
|
|
|
|
|
|
|
priv->rx_pause = pause->rx_pause ? true : false;
|
|
|
|
priv->tx_pause = pause->tx_pause ? true : false;
|
|
|
|
|
|
|
|
for_each_slave(priv, _cpsw_adjust_link, priv, &link);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-01-20 00:58:27 +08:00
|
|
|
static int cpsw_set_channels(struct net_device *ndev,
|
|
|
|
struct ethtool_channels *chs)
|
|
|
|
{
|
2019-04-27 01:12:42 +08:00
|
|
|
return cpsw_set_channels_common(ndev, chs, cpsw_rx_handler);
|
2017-01-07 04:07:34 +08:00
|
|
|
}
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
static const struct ethtool_ops cpsw_ethtool_ops = {
|
|
|
|
.get_drvinfo = cpsw_get_drvinfo,
|
|
|
|
.get_msglevel = cpsw_get_msglevel,
|
|
|
|
.set_msglevel = cpsw_set_msglevel,
|
|
|
|
.get_link = ethtool_op_get_link,
|
2012-10-29 16:45:20 +08:00
|
|
|
.get_ts_info = cpsw_get_ts_info,
|
2013-03-12 07:16:37 +08:00
|
|
|
.get_coalesce = cpsw_get_coalesce,
|
|
|
|
.set_coalesce = cpsw_set_coalesce,
|
2013-07-23 18:08:17 +08:00
|
|
|
.get_sset_count = cpsw_get_sset_count,
|
|
|
|
.get_strings = cpsw_get_strings,
|
|
|
|
.get_ethtool_stats = cpsw_get_ethtool_stats,
|
2014-09-09 01:24:02 +08:00
|
|
|
.get_pauseparam = cpsw_get_pauseparam,
|
|
|
|
.set_pauseparam = cpsw_set_pauseparam,
|
2013-08-20 13:59:38 +08:00
|
|
|
.get_wol = cpsw_get_wol,
|
|
|
|
.set_wol = cpsw_set_wol,
|
2014-07-23 01:55:07 +08:00
|
|
|
.get_regs_len = cpsw_get_regs_len,
|
|
|
|
.get_regs = cpsw_get_regs,
|
2016-06-25 02:23:44 +08:00
|
|
|
.begin = cpsw_ethtool_op_begin,
|
|
|
|
.complete = cpsw_ethtool_op_complete,
|
2016-08-23 02:18:28 +08:00
|
|
|
.get_channels = cpsw_get_channels,
|
|
|
|
.set_channels = cpsw_set_channels,
|
2016-10-08 23:46:15 +08:00
|
|
|
.get_link_ksettings = cpsw_get_link_ksettings,
|
|
|
|
.set_link_ksettings = cpsw_set_link_ksettings,
|
2016-11-28 16:41:33 +08:00
|
|
|
.get_eee = cpsw_get_eee,
|
|
|
|
.set_eee = cpsw_set_eee,
|
2016-11-28 17:47:52 +08:00
|
|
|
.nway_reset = cpsw_nway_reset,
|
2017-01-07 04:07:34 +08:00
|
|
|
.get_ringparam = cpsw_get_ringparam,
|
|
|
|
.set_ringparam = cpsw_set_ringparam,
|
2012-03-19 04:17:54 +08:00
|
|
|
};
|
|
|
|
|
2016-04-28 09:25:25 +08:00
|
|
|
static int cpsw_probe_dt(struct cpsw_platform_data *data,
|
2012-07-30 18:17:14 +08:00
|
|
|
struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct device_node *node = pdev->dev.of_node;
|
|
|
|
struct device_node *slave_node;
|
|
|
|
int i = 0, ret;
|
|
|
|
u32 prop;
|
|
|
|
|
|
|
|
if (!node)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (of_property_read_u32(node, "slaves", &prop)) {
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_err(&pdev->dev, "Missing slaves property in the DT.\n");
|
2012-07-30 18:17:14 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
data->slaves = prop;
|
|
|
|
|
2013-03-12 07:16:35 +08:00
|
|
|
if (of_property_read_u32(node, "active_slave", &prop)) {
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_err(&pdev->dev, "Missing active_slave property in the DT.\n");
|
2013-09-21 03:20:38 +08:00
|
|
|
return -EINVAL;
|
2012-10-29 16:45:18 +08:00
|
|
|
}
|
2013-03-12 07:16:35 +08:00
|
|
|
data->active_slave = prop;
|
2012-10-29 16:45:18 +08:00
|
|
|
|
treewide: devm_kzalloc() -> devm_kcalloc()
The devm_kzalloc() function has a 2-factor argument form, devm_kcalloc().
This patch replaces cases of:
devm_kzalloc(handle, a * b, gfp)
with:
devm_kcalloc(handle, a * b, gfp)
as well as handling cases of:
devm_kzalloc(handle, a * b * c, gfp)
with:
devm_kzalloc(handle, array3_size(a, b, c), gfp)
as it's slightly less ugly than:
devm_kcalloc(handle, array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
devm_kzalloc(handle, 4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
Some manual whitespace fixes were needed in this patch, as Coccinelle
really liked to write "=devm_kcalloc..." instead of "= devm_kcalloc...".
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
expression HANDLE;
type TYPE;
expression THING, E;
@@
(
devm_kzalloc(HANDLE,
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
devm_kzalloc(HANDLE,
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression HANDLE;
expression COUNT;
typedef u8;
typedef __u8;
@@
(
devm_kzalloc(HANDLE,
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(char) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
expression HANDLE;
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
expression HANDLE;
identifier SIZE, COUNT;
@@
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression HANDLE;
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
devm_kzalloc(HANDLE,
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression HANDLE;
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
expression HANDLE;
identifier STRIDE, SIZE, COUNT;
@@
(
devm_kzalloc(HANDLE,
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression HANDLE;
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
devm_kzalloc(HANDLE, C1 * C2 * C3, ...)
|
devm_kzalloc(HANDLE,
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression HANDLE;
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
devm_kzalloc(HANDLE, sizeof(THING) * C2, ...)
|
devm_kzalloc(HANDLE, sizeof(TYPE) * C2, ...)
|
devm_kzalloc(HANDLE, C1 * C2 * C3, ...)
|
devm_kzalloc(HANDLE, C1 * C2, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- (E1) * E2
+ E1, E2
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- (E1) * (E2)
+ E1, E2
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 05:07:58 +08:00
|
|
|
data->slave_data = devm_kcalloc(&pdev->dev,
|
|
|
|
data->slaves,
|
|
|
|
sizeof(struct cpsw_slave_data),
|
2013-09-21 03:20:38 +08:00
|
|
|
GFP_KERNEL);
|
2013-02-04 01:43:58 +08:00
|
|
|
if (!data->slave_data)
|
2013-09-21 03:20:38 +08:00
|
|
|
return -ENOMEM;
|
2012-07-30 18:17:14 +08:00
|
|
|
|
|
|
|
if (of_property_read_u32(node, "cpdma_channels", &prop)) {
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_err(&pdev->dev, "Missing cpdma_channels property in the DT.\n");
|
2013-09-21 03:20:38 +08:00
|
|
|
return -EINVAL;
|
2012-07-30 18:17:14 +08:00
|
|
|
}
|
|
|
|
data->channels = prop;
|
|
|
|
|
|
|
|
if (of_property_read_u32(node, "ale_entries", &prop)) {
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_err(&pdev->dev, "Missing ale_entries property in the DT.\n");
|
2013-09-21 03:20:38 +08:00
|
|
|
return -EINVAL;
|
2012-07-30 18:17:14 +08:00
|
|
|
}
|
|
|
|
data->ale_entries = prop;
|
|
|
|
|
|
|
|
if (of_property_read_u32(node, "bd_ram_size", &prop)) {
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_err(&pdev->dev, "Missing bd_ram_size property in the DT.\n");
|
2013-09-21 03:20:38 +08:00
|
|
|
return -EINVAL;
|
2012-07-30 18:17:14 +08:00
|
|
|
}
|
|
|
|
data->bd_ram_size = prop;
|
|
|
|
|
|
|
|
if (of_property_read_u32(node, "mac_control", &prop)) {
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_err(&pdev->dev, "Missing mac_control property in the DT.\n");
|
2013-09-21 03:20:38 +08:00
|
|
|
return -EINVAL;
|
2012-07-30 18:17:14 +08:00
|
|
|
}
|
|
|
|
data->mac_control = prop;
|
|
|
|
|
2013-10-04 20:44:40 +08:00
|
|
|
if (of_property_read_bool(node, "dual_emac"))
|
|
|
|
data->dual_emac = 1;
|
2013-02-11 17:52:20 +08:00
|
|
|
|
2012-11-14 17:07:56 +08:00
|
|
|
/*
|
|
|
|
* Populate all the child nodes here...
|
|
|
|
*/
|
|
|
|
ret = of_platform_populate(node, NULL, NULL, &pdev->dev);
|
|
|
|
/* We do not want to force this, as in some cases may not have child */
|
|
|
|
if (ret)
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_warn(&pdev->dev, "Doesn't have any child node\n");
|
2012-11-14 17:07:56 +08:00
|
|
|
|
2016-06-21 08:16:31 +08:00
|
|
|
for_each_available_child_of_node(node, slave_node) {
|
2012-07-30 18:17:14 +08:00
|
|
|
struct cpsw_slave_data *slave_data = data->slave_data + i;
|
|
|
|
const void *mac_addr = NULL;
|
2012-11-14 17:07:56 +08:00
|
|
|
int lenp;
|
|
|
|
const __be32 *parp;
|
|
|
|
|
2013-10-04 20:44:39 +08:00
|
|
|
/* This is no slave child node, continue */
|
2018-12-06 03:50:32 +08:00
|
|
|
if (!of_node_name_eq(slave_node, "slave"))
|
2013-10-04 20:44:39 +08:00
|
|
|
continue;
|
|
|
|
|
2018-11-26 08:15:25 +08:00
|
|
|
slave_data->ifphy = devm_of_phy_get(&pdev->dev, slave_node,
|
|
|
|
NULL);
|
|
|
|
if (!IS_ENABLED(CONFIG_TI_CPSW_PHY_SEL) &&
|
|
|
|
IS_ERR(slave_data->ifphy)) {
|
|
|
|
ret = PTR_ERR(slave_data->ifphy);
|
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"%d: Error retrieving port phy: %d\n", i, ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-04-28 09:25:25 +08:00
|
|
|
slave_data->phy_node = of_parse_phandle(slave_node,
|
|
|
|
"phy-handle", 0);
|
2015-12-17 12:02:10 +08:00
|
|
|
parp = of_get_property(slave_node, "phy_id", &lenp);
|
2016-04-28 09:38:26 +08:00
|
|
|
if (slave_data->phy_node) {
|
|
|
|
dev_dbg(&pdev->dev,
|
2017-07-19 05:43:19 +08:00
|
|
|
"slave[%d] using phy-handle=\"%pOF\"\n",
|
|
|
|
i, slave_data->phy_node);
|
2016-04-28 09:38:26 +08:00
|
|
|
} else if (of_phy_is_fixed_link(slave_node)) {
|
2015-12-17 12:02:11 +08:00
|
|
|
/* In the case of a fixed PHY, the DT node associated
|
|
|
|
* to the PHY is the Ethernet MAC DT node.
|
|
|
|
*/
|
2015-11-04 05:09:51 +08:00
|
|
|
ret = of_phy_register_fixed_link(slave_node);
|
2016-11-18 00:40:04 +08:00
|
|
|
if (ret) {
|
|
|
|
if (ret != -EPROBE_DEFER)
|
|
|
|
dev_err(&pdev->dev, "failed to register fixed-link phy: %d\n", ret);
|
2015-11-04 05:09:51 +08:00
|
|
|
return ret;
|
2016-11-18 00:40:04 +08:00
|
|
|
}
|
2016-04-28 09:45:45 +08:00
|
|
|
slave_data->phy_node = of_node_get(slave_node);
|
2015-12-17 12:02:10 +08:00
|
|
|
} else if (parp) {
|
|
|
|
u32 phyid;
|
|
|
|
struct device_node *mdio_node;
|
|
|
|
struct platform_device *mdio;
|
|
|
|
|
|
|
|
if (lenp != (sizeof(__be32) * 2)) {
|
|
|
|
dev_err(&pdev->dev, "Invalid slave[%d] phy_id property\n", i);
|
|
|
|
goto no_phy_slave;
|
|
|
|
}
|
|
|
|
mdio_node = of_find_node_by_phandle(be32_to_cpup(parp));
|
|
|
|
phyid = be32_to_cpup(parp+1);
|
|
|
|
mdio = of_find_device_by_node(mdio_node);
|
|
|
|
of_node_put(mdio_node);
|
|
|
|
if (!mdio) {
|
|
|
|
dev_err(&pdev->dev, "Missing mdio platform device\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
snprintf(slave_data->phy_id, sizeof(slave_data->phy_id),
|
|
|
|
PHY_ID_FMT, mdio->name, phyid);
|
2016-11-18 00:39:59 +08:00
|
|
|
put_device(&mdio->dev);
|
2015-12-17 12:02:10 +08:00
|
|
|
} else {
|
2016-04-28 09:38:26 +08:00
|
|
|
dev_err(&pdev->dev,
|
|
|
|
"No slave[%d] phy_id, phy-handle, or fixed-link property\n",
|
|
|
|
i);
|
2014-10-24 21:21:33 +08:00
|
|
|
goto no_phy_slave;
|
2012-07-30 18:17:14 +08:00
|
|
|
}
|
2014-10-24 21:21:33 +08:00
|
|
|
slave_data->phy_if = of_get_phy_mode(slave_node);
|
|
|
|
if (slave_data->phy_if < 0) {
|
|
|
|
dev_err(&pdev->dev, "Missing or malformed slave[%d] phy-mode property\n",
|
|
|
|
i);
|
|
|
|
return slave_data->phy_if;
|
|
|
|
}
|
|
|
|
|
|
|
|
no_phy_slave:
|
2012-07-30 18:17:14 +08:00
|
|
|
mac_addr = of_get_mac_address(slave_node);
|
2019-05-07 05:27:04 +08:00
|
|
|
if (!IS_ERR(mac_addr)) {
|
net: ethernet: fix similar warning reported by kbuild test robot
This patch fixes following (similar) warning reported by kbuild test robot:
In function ‘memcpy’,
inlined from ‘smsc75xx_init_mac_address’ at drivers/net/usb/smsc75xx.c:778:3,
inlined from ‘smsc75xx_bind’ at drivers/net/usb/smsc75xx.c:1501:2:
./include/linux/string.h:355:9: warning: argument 2 null where non-null expected [-Wnonnull]
return __builtin_memcpy(p, q, size);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/net/usb/smsc75xx.c: In function ‘smsc75xx_bind’:
./include/linux/string.h:355:9: note: in a call to built-in function ‘__builtin_memcpy’
I've replaced the offending memcpy with ether_addr_copy, because I'm
100% sure, that of_get_mac_address can't return NULL as it returns valid
pointer or ERR_PTR encoded value, nothing else.
I'm hesitant to just change IS_ERR into IS_ERR_OR_NULL check, as this
would make the warning disappear also, but it would be confusing to
check for impossible return value just to make a compiler happy.
I'm now changing all occurencies of memcpy to ether_addr_copy after the
of_get_mac_address call, as it's very likely, that we're going to get
similar reports from kbuild test robot in the future.
Fixes: a51645f70f63 ("net: ethernet: support of_get_mac_address new ERR_PTR error")
Reported-by: kbuild test robot <lkp@intel.com>
Signed-off-by: Petr Štetiar <ynezz@true.cz>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-05-10 17:35:17 +08:00
|
|
|
ether_addr_copy(slave_data->mac_addr, mac_addr);
|
2014-09-29 14:53:17 +08:00
|
|
|
} else {
|
2015-09-21 18:26:50 +08:00
|
|
|
ret = ti_cm_get_macid(&pdev->dev, i,
|
|
|
|
slave_data->mac_addr);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2014-09-29 14:53:17 +08:00
|
|
|
}
|
2013-02-11 17:52:20 +08:00
|
|
|
if (data->dual_emac) {
|
2013-04-15 15:31:28 +08:00
|
|
|
if (of_property_read_u32(slave_node, "dual_emac_res_vlan",
|
2013-02-11 17:52:20 +08:00
|
|
|
&prop)) {
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_err(&pdev->dev, "Missing dual_emac_res_vlan in DT.\n");
|
2013-02-11 17:52:20 +08:00
|
|
|
slave_data->dual_emac_res_vlan = i+1;
|
2014-05-12 12:51:19 +08:00
|
|
|
dev_err(&pdev->dev, "Using %d as Reserved VLAN for %d slave\n",
|
|
|
|
slave_data->dual_emac_res_vlan, i);
|
2013-02-11 17:52:20 +08:00
|
|
|
} else {
|
|
|
|
slave_data->dual_emac_res_vlan = prop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-30 18:17:14 +08:00
|
|
|
i++;
|
2013-12-02 15:23:39 +08:00
|
|
|
if (i == data->slaves)
|
|
|
|
break;
|
2012-07-30 18:17:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-18 00:40:00 +08:00
|
|
|
static void cpsw_remove_dt(struct platform_device *pdev)
|
|
|
|
{
|
2016-11-18 00:40:01 +08:00
|
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
|
|
|
struct cpsw_platform_data *data = &cpsw->data;
|
|
|
|
struct device_node *node = pdev->dev.of_node;
|
|
|
|
struct device_node *slave_node;
|
|
|
|
int i = 0;
|
|
|
|
|
|
|
|
for_each_available_child_of_node(node, slave_node) {
|
|
|
|
struct cpsw_slave_data *slave_data = &data->slave_data[i];
|
|
|
|
|
2018-12-06 03:50:32 +08:00
|
|
|
if (!of_node_name_eq(slave_node, "slave"))
|
2016-11-18 00:40:01 +08:00
|
|
|
continue;
|
|
|
|
|
2016-11-29 02:24:55 +08:00
|
|
|
if (of_phy_is_fixed_link(slave_node))
|
|
|
|
of_phy_deregister_fixed_link(slave_node);
|
2016-11-18 00:40:01 +08:00
|
|
|
|
|
|
|
of_node_put(slave_data->phy_node);
|
|
|
|
|
|
|
|
i++;
|
|
|
|
if (i == data->slaves)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2016-11-18 00:40:00 +08:00
|
|
|
of_platform_depopulate(&pdev->dev);
|
|
|
|
}
|
|
|
|
|
2016-08-10 07:22:38 +08:00
|
|
|
static int cpsw_probe_dual_emac(struct cpsw_priv *priv)
|
2013-02-11 17:52:20 +08:00
|
|
|
{
|
2016-08-10 07:22:42 +08:00
|
|
|
struct cpsw_common *cpsw = priv->cpsw;
|
|
|
|
struct cpsw_platform_data *data = &cpsw->data;
|
2013-02-11 17:52:20 +08:00
|
|
|
struct net_device *ndev;
|
|
|
|
struct cpsw_priv *priv_sl2;
|
2016-08-10 07:22:41 +08:00
|
|
|
int ret = 0;
|
2013-02-11 17:52:20 +08:00
|
|
|
|
2019-04-27 01:12:29 +08:00
|
|
|
ndev = devm_alloc_etherdev_mqs(cpsw->dev, sizeof(struct cpsw_priv),
|
|
|
|
CPSW_MAX_QUEUES, CPSW_MAX_QUEUES);
|
2013-02-11 17:52:20 +08:00
|
|
|
if (!ndev) {
|
2016-08-10 07:22:38 +08:00
|
|
|
dev_err(cpsw->dev, "cpsw: error allocating net_device\n");
|
2013-02-11 17:52:20 +08:00
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
priv_sl2 = netdev_priv(ndev);
|
2016-08-10 07:22:42 +08:00
|
|
|
priv_sl2->cpsw = cpsw;
|
2013-02-11 17:52:20 +08:00
|
|
|
priv_sl2->ndev = ndev;
|
|
|
|
priv_sl2->dev = &ndev->dev;
|
|
|
|
priv_sl2->msg_enable = netif_msg_init(debug_level, CPSW_DEBUG);
|
|
|
|
|
|
|
|
if (is_valid_ether_addr(data->slave_data[1].mac_addr)) {
|
|
|
|
memcpy(priv_sl2->mac_addr, data->slave_data[1].mac_addr,
|
|
|
|
ETH_ALEN);
|
2016-08-10 07:22:38 +08:00
|
|
|
dev_info(cpsw->dev, "cpsw: Detected MACID = %pM\n",
|
|
|
|
priv_sl2->mac_addr);
|
2013-02-11 17:52:20 +08:00
|
|
|
} else {
|
2018-06-23 01:51:00 +08:00
|
|
|
eth_random_addr(priv_sl2->mac_addr);
|
2016-08-10 07:22:38 +08:00
|
|
|
dev_info(cpsw->dev, "cpsw: Random MACID = %pM\n",
|
|
|
|
priv_sl2->mac_addr);
|
2013-02-11 17:52:20 +08:00
|
|
|
}
|
|
|
|
memcpy(ndev->dev_addr, priv_sl2->mac_addr, ETH_ALEN);
|
|
|
|
|
|
|
|
priv_sl2->emac_port = 1;
|
2016-08-10 07:22:42 +08:00
|
|
|
cpsw->slaves[1].ndev = ndev;
|
2018-07-28 00:54:39 +08:00
|
|
|
ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_VLAN_CTAG_RX;
|
2013-02-11 17:52:20 +08:00
|
|
|
|
|
|
|
ndev->netdev_ops = &cpsw_netdev_ops;
|
2014-05-11 08:12:32 +08:00
|
|
|
ndev->ethtool_ops = &cpsw_ethtool_ops;
|
2013-02-11 17:52:20 +08:00
|
|
|
|
|
|
|
/* register the network device */
|
2016-08-10 07:22:38 +08:00
|
|
|
SET_NETDEV_DEV(ndev, cpsw->dev);
|
2013-02-11 17:52:20 +08:00
|
|
|
ret = register_netdev(ndev);
|
2019-04-27 01:12:29 +08:00
|
|
|
if (ret)
|
2016-08-10 07:22:38 +08:00
|
|
|
dev_err(cpsw->dev, "cpsw: error registering net device\n");
|
2013-02-11 17:52:20 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-08-12 17:52:53 +08:00
|
|
|
static const struct of_device_id cpsw_of_mtable[] = {
|
2018-05-17 06:21:45 +08:00
|
|
|
{ .compatible = "ti,cpsw"},
|
|
|
|
{ .compatible = "ti,am335x-cpsw"},
|
|
|
|
{ .compatible = "ti,am4372-cpsw"},
|
|
|
|
{ .compatible = "ti,dra7-cpsw"},
|
2015-08-12 17:52:53 +08:00
|
|
|
{ /* sentinel */ },
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, cpsw_of_mtable);
|
|
|
|
|
2018-05-17 06:21:45 +08:00
|
|
|
static const struct soc_device_attribute cpsw_soc_devices[] = {
|
|
|
|
{ .family = "AM33xx", .revision = "ES1.0"},
|
|
|
|
{ /* sentinel */ }
|
|
|
|
};
|
|
|
|
|
2012-12-03 22:23:45 +08:00
|
|
|
static int cpsw_probe(struct platform_device *pdev)
|
2012-03-19 04:17:54 +08:00
|
|
|
{
|
2019-04-27 01:12:27 +08:00
|
|
|
struct device *dev = &pdev->dev;
|
2016-08-10 07:22:35 +08:00
|
|
|
struct clk *clk;
|
2013-04-24 16:48:23 +08:00
|
|
|
struct cpsw_platform_data *data;
|
2012-03-19 04:17:54 +08:00
|
|
|
struct net_device *ndev;
|
|
|
|
struct cpsw_priv *priv;
|
2013-09-21 03:20:38 +08:00
|
|
|
void __iomem *ss_regs;
|
|
|
|
struct resource *res, *ss_res;
|
2015-09-07 17:46:44 +08:00
|
|
|
struct gpio_descs *mode;
|
2018-05-17 06:21:45 +08:00
|
|
|
const struct soc_device_attribute *soc;
|
2016-08-10 07:22:37 +08:00
|
|
|
struct cpsw_common *cpsw;
|
2019-04-27 01:12:39 +08:00
|
|
|
int ret = 0, ch;
|
2015-01-17 00:11:11 +08:00
|
|
|
int irq;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2019-04-27 01:12:27 +08:00
|
|
|
cpsw = devm_kzalloc(dev, sizeof(struct cpsw_common), GFP_KERNEL);
|
2016-11-18 00:40:03 +08:00
|
|
|
if (!cpsw)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2019-04-27 01:12:27 +08:00
|
|
|
cpsw->dev = dev;
|
2016-08-10 07:22:37 +08:00
|
|
|
|
2019-04-27 01:12:27 +08:00
|
|
|
mode = devm_gpiod_get_array_optional(dev, "mode", GPIOD_OUT_LOW);
|
2015-09-07 17:46:44 +08:00
|
|
|
if (IS_ERR(mode)) {
|
|
|
|
ret = PTR_ERR(mode);
|
2019-04-27 01:12:27 +08:00
|
|
|
dev_err(dev, "gpio request failed, ret %d\n", ret);
|
2019-04-27 01:12:29 +08:00
|
|
|
return ret;
|
2015-09-07 17:46:44 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
clk = devm_clk_get(dev, "fck");
|
|
|
|
if (IS_ERR(clk)) {
|
2019-04-30 09:55:24 +08:00
|
|
|
ret = PTR_ERR(clk);
|
2019-04-27 01:12:36 +08:00
|
|
|
dev_err(dev, "fck is not found %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
cpsw->bus_freq_mhz = clk_get_rate(clk) / 1000000;
|
|
|
|
|
|
|
|
ss_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
ss_regs = devm_ioremap_resource(dev, ss_res);
|
|
|
|
if (IS_ERR(ss_regs))
|
|
|
|
return PTR_ERR(ss_regs);
|
|
|
|
cpsw->regs = ss_regs;
|
|
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
|
|
|
|
cpsw->wr_regs = devm_ioremap_resource(dev, res);
|
|
|
|
if (IS_ERR(cpsw->wr_regs))
|
|
|
|
return PTR_ERR(cpsw->wr_regs);
|
|
|
|
|
|
|
|
/* RX IRQ */
|
|
|
|
irq = platform_get_irq(pdev, 1);
|
|
|
|
if (irq < 0)
|
|
|
|
return irq;
|
|
|
|
cpsw->irqs_table[0] = irq;
|
|
|
|
|
|
|
|
/* TX IRQ */
|
|
|
|
irq = platform_get_irq(pdev, 2);
|
|
|
|
if (irq < 0)
|
|
|
|
return irq;
|
|
|
|
cpsw->irqs_table[1] = irq;
|
|
|
|
|
net: cpsw: Add parent<->child relation support between cpsw and mdio
CPGMAC SubSystem consist of various sub-modules, like, mdio, cpdma,
cpsw, etc... These sub-modules are also used in some of Davinci family
of devices. Now based on requirement, use-case and available technology
nodes the integration of these sub-modules varies across devices.
So coming back to Linux net driver, currently separate and independent
platform devices & drivers for CPSW and MDIO is implemented. In case of
Davinci they both has separate control, from resources perspective,
like clock.
In case of AM33XX, the resources are shared and only one register
bit-field is provided to control module/clock enable/disable, makes it
difficult to handle common resource.
So the solution here implemented in this patch is,
Create parent<->child relationship between both the drivers, making
CPSW as a parent and MDIO as its child and enumerate all the child nodes
under CPSW module.
Both the drivers will function exactly the way it was operating before,
including runtime-pm functionality. No change is required in MDIO driver
(for that matter to any child driver).
As this is only supported during DT boot, the parent<->child relationship
is created and populated in DT execution flow. The only required change
is inside DTS file, making MDIO as a child to CPSW node.
Signed-off-by: Vaibhav Hiremath <hvaibhav@ti.com>
Signed-off-by: Mugunthan V N <mugunthanvnm@ti.com>
Acked-by: Peter Korsgaard <jacmet@sunsite.dk>
Acked-by: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2012-11-14 17:07:55 +08:00
|
|
|
/*
|
|
|
|
* This may be required here for child devices.
|
|
|
|
*/
|
2019-04-27 01:12:27 +08:00
|
|
|
pm_runtime_enable(dev);
|
net: cpsw: Add parent<->child relation support between cpsw and mdio
CPGMAC SubSystem consist of various sub-modules, like, mdio, cpdma,
cpsw, etc... These sub-modules are also used in some of Davinci family
of devices. Now based on requirement, use-case and available technology
nodes the integration of these sub-modules varies across devices.
So coming back to Linux net driver, currently separate and independent
platform devices & drivers for CPSW and MDIO is implemented. In case of
Davinci they both has separate control, from resources perspective,
like clock.
In case of AM33XX, the resources are shared and only one register
bit-field is provided to control module/clock enable/disable, makes it
difficult to handle common resource.
So the solution here implemented in this patch is,
Create parent<->child relationship between both the drivers, making
CPSW as a parent and MDIO as its child and enumerate all the child nodes
under CPSW module.
Both the drivers will function exactly the way it was operating before,
including runtime-pm functionality. No change is required in MDIO driver
(for that matter to any child driver).
As this is only supported during DT boot, the parent<->child relationship
is created and populated in DT execution flow. The only required change
is inside DTS file, making MDIO as a child to CPSW node.
Signed-off-by: Vaibhav Hiremath <hvaibhav@ti.com>
Signed-off-by: Mugunthan V N <mugunthanvnm@ti.com>
Acked-by: Peter Korsgaard <jacmet@sunsite.dk>
Acked-by: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2012-11-14 17:07:55 +08:00
|
|
|
|
2016-11-18 00:40:00 +08:00
|
|
|
/* Need to enable clocks with runtime PM api to access module
|
|
|
|
* registers
|
|
|
|
*/
|
2019-04-27 01:12:27 +08:00
|
|
|
ret = pm_runtime_get_sync(dev);
|
2016-11-18 00:40:00 +08:00
|
|
|
if (ret < 0) {
|
2019-04-27 01:12:27 +08:00
|
|
|
pm_runtime_put_noidle(dev);
|
2013-09-21 03:20:38 +08:00
|
|
|
goto clean_runtime_disable_ret;
|
2012-07-30 18:17:14 +08:00
|
|
|
}
|
2016-11-18 00:40:00 +08:00
|
|
|
|
2016-11-18 00:40:04 +08:00
|
|
|
ret = cpsw_probe_dt(&cpsw->data, pdev);
|
|
|
|
if (ret)
|
2016-11-18 00:40:00 +08:00
|
|
|
goto clean_dt_ret;
|
2016-11-18 00:40:04 +08:00
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
soc = soc_device_match(cpsw_soc_devices);
|
|
|
|
if (soc)
|
|
|
|
cpsw->quirk_irq = 1;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
data = &cpsw->data;
|
2019-04-27 01:12:27 +08:00
|
|
|
cpsw->slaves = devm_kcalloc(dev,
|
treewide: devm_kzalloc() -> devm_kcalloc()
The devm_kzalloc() function has a 2-factor argument form, devm_kcalloc().
This patch replaces cases of:
devm_kzalloc(handle, a * b, gfp)
with:
devm_kcalloc(handle, a * b, gfp)
as well as handling cases of:
devm_kzalloc(handle, a * b * c, gfp)
with:
devm_kzalloc(handle, array3_size(a, b, c), gfp)
as it's slightly less ugly than:
devm_kcalloc(handle, array_size(a, b), c, gfp)
This does, however, attempt to ignore constant size factors like:
devm_kzalloc(handle, 4 * 1024, gfp)
though any constants defined via macros get caught up in the conversion.
Any factors with a sizeof() of "unsigned char", "char", and "u8" were
dropped, since they're redundant.
Some manual whitespace fixes were needed in this patch, as Coccinelle
really liked to write "=devm_kcalloc..." instead of "= devm_kcalloc...".
The Coccinelle script used for this was:
// Fix redundant parens around sizeof().
@@
expression HANDLE;
type TYPE;
expression THING, E;
@@
(
devm_kzalloc(HANDLE,
- (sizeof(TYPE)) * E
+ sizeof(TYPE) * E
, ...)
|
devm_kzalloc(HANDLE,
- (sizeof(THING)) * E
+ sizeof(THING) * E
, ...)
)
// Drop single-byte sizes and redundant parens.
@@
expression HANDLE;
expression COUNT;
typedef u8;
typedef __u8;
@@
(
devm_kzalloc(HANDLE,
- sizeof(u8) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(__u8) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(char) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(unsigned char) * (COUNT)
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(u8) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(__u8) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(char) * COUNT
+ COUNT
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(unsigned char) * COUNT
+ COUNT
, ...)
)
// 2-factor product with sizeof(type/expression) and identifier or constant.
@@
expression HANDLE;
type TYPE;
expression THING;
identifier COUNT_ID;
constant COUNT_CONST;
@@
(
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (COUNT_ID)
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * COUNT_ID
+ COUNT_ID, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (COUNT_CONST)
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * COUNT_CONST
+ COUNT_CONST, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (COUNT_ID)
+ COUNT_ID, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * COUNT_ID
+ COUNT_ID, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (COUNT_CONST)
+ COUNT_CONST, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * COUNT_CONST
+ COUNT_CONST, sizeof(THING)
, ...)
)
// 2-factor product, only identifiers.
@@
expression HANDLE;
identifier SIZE, COUNT;
@@
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- SIZE * COUNT
+ COUNT, SIZE
, ...)
// 3-factor product with 1 sizeof(type) or sizeof(expression), with
// redundant parens removed.
@@
expression HANDLE;
expression THING;
identifier STRIDE, COUNT;
type TYPE;
@@
(
devm_kzalloc(HANDLE,
- sizeof(TYPE) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(TYPE))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * (COUNT) * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * (COUNT) * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * COUNT * (STRIDE)
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING) * COUNT * STRIDE
+ array3_size(COUNT, STRIDE, sizeof(THING))
, ...)
)
// 3-factor product with 2 sizeof(variable), with redundant parens removed.
@@
expression HANDLE;
expression THING1, THING2;
identifier COUNT;
type TYPE1, TYPE2;
@@
(
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(TYPE2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(TYPE2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(THING1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(THING1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * COUNT
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
|
devm_kzalloc(HANDLE,
- sizeof(TYPE1) * sizeof(THING2) * (COUNT)
+ array3_size(COUNT, sizeof(TYPE1), sizeof(THING2))
, ...)
)
// 3-factor product, only identifiers, with redundant parens removed.
@@
expression HANDLE;
identifier STRIDE, SIZE, COUNT;
@@
(
devm_kzalloc(HANDLE,
- (COUNT) * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * (STRIDE) * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * STRIDE * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- (COUNT) * (STRIDE) * (SIZE)
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
|
devm_kzalloc(HANDLE,
- COUNT * STRIDE * SIZE
+ array3_size(COUNT, STRIDE, SIZE)
, ...)
)
// Any remaining multi-factor products, first at least 3-factor products,
// when they're not all constants...
@@
expression HANDLE;
expression E1, E2, E3;
constant C1, C2, C3;
@@
(
devm_kzalloc(HANDLE, C1 * C2 * C3, ...)
|
devm_kzalloc(HANDLE,
- (E1) * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- (E1) * (E2) * E3
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- (E1) * (E2) * (E3)
+ array3_size(E1, E2, E3)
, ...)
|
devm_kzalloc(HANDLE,
- E1 * E2 * E3
+ array3_size(E1, E2, E3)
, ...)
)
// And then all remaining 2 factors products when they're not all constants,
// keeping sizeof() as the second factor argument.
@@
expression HANDLE;
expression THING, E1, E2;
type TYPE;
constant C1, C2, C3;
@@
(
devm_kzalloc(HANDLE, sizeof(THING) * C2, ...)
|
devm_kzalloc(HANDLE, sizeof(TYPE) * C2, ...)
|
devm_kzalloc(HANDLE, C1 * C2 * C3, ...)
|
devm_kzalloc(HANDLE, C1 * C2, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * (E2)
+ E2, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(TYPE) * E2
+ E2, sizeof(TYPE)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * (E2)
+ E2, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- sizeof(THING) * E2
+ E2, sizeof(THING)
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- (E1) * E2
+ E1, E2
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- (E1) * (E2)
+ E1, E2
, ...)
|
- devm_kzalloc
+ devm_kcalloc
(HANDLE,
- E1 * E2
+ E1, E2
, ...)
)
Signed-off-by: Kees Cook <keescook@chromium.org>
2018-06-13 05:07:58 +08:00
|
|
|
data->slaves, sizeof(struct cpsw_slave),
|
2013-09-21 03:20:38 +08:00
|
|
|
GFP_KERNEL);
|
2016-08-10 07:22:42 +08:00
|
|
|
if (!cpsw->slaves) {
|
2013-09-21 03:20:38 +08:00
|
|
|
ret = -ENOMEM;
|
2016-11-18 00:40:00 +08:00
|
|
|
goto clean_dt_ret;
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
cpsw->rx_packet_max = max(rx_packet_max, CPSW_MAX_PACKET_SIZE);
|
2019-04-27 01:12:42 +08:00
|
|
|
cpsw->descs_pool_size = descs_pool_size;
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2019-04-27 01:12:39 +08:00
|
|
|
ret = cpsw_init_common(cpsw, ss_regs, ale_ageout,
|
|
|
|
ss_res->start + CPSW2_BD_OFFSET,
|
|
|
|
descs_pool_size);
|
|
|
|
if (ret)
|
2016-11-18 00:40:00 +08:00
|
|
|
goto clean_dt_ret;
|
2018-05-17 06:21:45 +08:00
|
|
|
|
2018-07-24 05:26:29 +08:00
|
|
|
ch = cpsw->quirk_irq ? 0 : 7;
|
|
|
|
cpsw->txv[0].ch = cpdma_chan_create(cpsw->dma, ch, cpsw_tx_handler, 0);
|
2017-12-13 05:06:35 +08:00
|
|
|
if (IS_ERR(cpsw->txv[0].ch)) {
|
2019-04-27 01:12:27 +08:00
|
|
|
dev_err(dev, "error initializing tx dma channel\n");
|
2017-12-13 05:06:35 +08:00
|
|
|
ret = PTR_ERR(cpsw->txv[0].ch);
|
2019-04-27 01:12:36 +08:00
|
|
|
goto clean_cpts;
|
2017-12-13 05:06:35 +08:00
|
|
|
}
|
|
|
|
|
2016-11-29 23:00:51 +08:00
|
|
|
cpsw->rxv[0].ch = cpdma_chan_create(cpsw->dma, 0, cpsw_rx_handler, 1);
|
2017-12-13 05:06:35 +08:00
|
|
|
if (IS_ERR(cpsw->rxv[0].ch)) {
|
2019-04-27 01:12:27 +08:00
|
|
|
dev_err(dev, "error initializing rx dma channel\n");
|
2017-12-13 05:06:35 +08:00
|
|
|
ret = PTR_ERR(cpsw->rxv[0].ch);
|
2019-04-27 01:12:36 +08:00
|
|
|
goto clean_cpts;
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
2019-04-27 01:12:36 +08:00
|
|
|
cpsw_split_res(cpsw);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
/* setup netdev */
|
|
|
|
ndev = devm_alloc_etherdev_mqs(dev, sizeof(struct cpsw_priv),
|
|
|
|
CPSW_MAX_QUEUES, CPSW_MAX_QUEUES);
|
|
|
|
if (!ndev) {
|
|
|
|
dev_err(dev, "error allocating net_device\n");
|
|
|
|
goto clean_cpts;
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
platform_set_drvdata(pdev, ndev);
|
|
|
|
priv = netdev_priv(ndev);
|
|
|
|
priv->cpsw = cpsw;
|
|
|
|
priv->ndev = ndev;
|
|
|
|
priv->dev = dev;
|
|
|
|
priv->msg_enable = netif_msg_init(debug_level, CPSW_DEBUG);
|
|
|
|
priv->emac_port = 0;
|
2016-12-07 08:00:41 +08:00
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
if (is_valid_ether_addr(data->slave_data[0].mac_addr)) {
|
|
|
|
memcpy(priv->mac_addr, data->slave_data[0].mac_addr, ETH_ALEN);
|
|
|
|
dev_info(dev, "Detected MACID = %pM\n", priv->mac_addr);
|
|
|
|
} else {
|
|
|
|
eth_random_addr(priv->mac_addr);
|
|
|
|
dev_info(dev, "Random MACID = %pM\n", priv->mac_addr);
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
memcpy(ndev->dev_addr, priv->mac_addr, ETH_ALEN);
|
|
|
|
|
|
|
|
cpsw->slaves[0].ndev = ndev;
|
|
|
|
|
2018-03-16 04:15:50 +08:00
|
|
|
ndev->features |= NETIF_F_HW_VLAN_CTAG_FILTER | NETIF_F_HW_VLAN_CTAG_RX;
|
2017-07-20 19:29:52 +08:00
|
|
|
|
|
|
|
ndev->netdev_ops = &cpsw_netdev_ops;
|
|
|
|
ndev->ethtool_ops = &cpsw_ethtool_ops;
|
2018-05-17 06:21:45 +08:00
|
|
|
netif_napi_add(ndev, &cpsw->napi_rx,
|
|
|
|
cpsw->quirk_irq ? cpsw_rx_poll : cpsw_rx_mq_poll,
|
|
|
|
CPSW_POLL_WEIGHT);
|
|
|
|
netif_tx_napi_add(ndev, &cpsw->napi_tx,
|
|
|
|
cpsw->quirk_irq ? cpsw_tx_poll : cpsw_tx_mq_poll,
|
|
|
|
CPSW_POLL_WEIGHT);
|
2017-07-20 19:29:52 +08:00
|
|
|
|
|
|
|
/* register the network device */
|
2019-04-27 01:12:27 +08:00
|
|
|
SET_NETDEV_DEV(ndev, dev);
|
2017-07-20 19:29:52 +08:00
|
|
|
ret = register_netdev(ndev);
|
|
|
|
if (ret) {
|
2019-04-27 01:12:27 +08:00
|
|
|
dev_err(dev, "error registering net device\n");
|
2017-07-20 19:29:52 +08:00
|
|
|
ret = -ENODEV;
|
2019-04-27 01:12:36 +08:00
|
|
|
goto clean_cpts;
|
2017-07-20 19:29:52 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (cpsw->data.dual_emac) {
|
|
|
|
ret = cpsw_probe_dual_emac(priv);
|
|
|
|
if (ret) {
|
|
|
|
cpsw_err(priv, probe, "error probe slave 2 emac interface\n");
|
|
|
|
goto clean_unregister_netdev_ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-17 00:11:12 +08:00
|
|
|
/* Grab RX and TX IRQs. Note that we also have RX_THRESHOLD and
|
|
|
|
* MISC IRQs which are always kept disabled with this driver so
|
|
|
|
* we will not request them.
|
|
|
|
*
|
|
|
|
* If anyone wants to implement support for those, make sure to
|
|
|
|
* first request and append them to irqs_table array.
|
|
|
|
*/
|
2019-04-27 01:12:36 +08:00
|
|
|
ret = devm_request_irq(dev, cpsw->irqs_table[0], cpsw_rx_interrupt,
|
2019-04-27 01:12:27 +08:00
|
|
|
0, dev_name(dev), cpsw);
|
2015-01-17 00:11:11 +08:00
|
|
|
if (ret < 0) {
|
2019-04-27 01:12:27 +08:00
|
|
|
dev_err(dev, "error attaching irq (%d)\n", ret);
|
2019-04-27 01:12:36 +08:00
|
|
|
goto clean_unregister_netdev_ret;
|
2015-01-17 00:11:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-04-27 01:12:36 +08:00
|
|
|
ret = devm_request_irq(dev, cpsw->irqs_table[1], cpsw_tx_interrupt,
|
2016-08-10 07:22:43 +08:00
|
|
|
0, dev_name(&pdev->dev), cpsw);
|
2015-01-17 00:11:11 +08:00
|
|
|
if (ret < 0) {
|
2019-04-27 01:12:27 +08:00
|
|
|
dev_err(dev, "error attaching irq (%d)\n", ret);
|
2019-04-27 01:12:36 +08:00
|
|
|
goto clean_unregister_netdev_ret;
|
2012-03-19 04:17:54 +08:00
|
|
|
}
|
2014-09-04 15:00:23 +08:00
|
|
|
|
2017-01-07 04:07:33 +08:00
|
|
|
cpsw_notice(priv, probe,
|
|
|
|
"initialized device (regs %pa, irq %d, pool size %d)\n",
|
2019-04-27 01:12:36 +08:00
|
|
|
&ss_res->start, cpsw->irqs_table[0], descs_pool_size);
|
2013-02-11 17:52:20 +08:00
|
|
|
|
2016-11-18 00:39:58 +08:00
|
|
|
pm_runtime_put(&pdev->dev);
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
return 0;
|
|
|
|
|
2016-11-18 00:40:02 +08:00
|
|
|
clean_unregister_netdev_ret:
|
|
|
|
unregister_netdev(ndev);
|
2019-04-27 01:12:36 +08:00
|
|
|
clean_cpts:
|
|
|
|
cpts_release(cpsw->cpts);
|
2016-08-10 07:22:40 +08:00
|
|
|
cpdma_ctlr_destroy(cpsw->dma);
|
2016-11-18 00:40:00 +08:00
|
|
|
clean_dt_ret:
|
|
|
|
cpsw_remove_dt(pdev);
|
2016-11-18 00:39:58 +08:00
|
|
|
pm_runtime_put_sync(&pdev->dev);
|
2013-09-21 03:20:38 +08:00
|
|
|
clean_runtime_disable_ret:
|
2012-07-17 16:09:50 +08:00
|
|
|
pm_runtime_disable(&pdev->dev);
|
2012-03-19 04:17:54 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-12-03 22:23:45 +08:00
|
|
|
static int cpsw_remove(struct platform_device *pdev)
|
2012-03-19 04:17:54 +08:00
|
|
|
{
|
|
|
|
struct net_device *ndev = platform_get_drvdata(pdev);
|
2016-08-10 07:22:44 +08:00
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
2016-07-29 01:50:35 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = pm_runtime_get_sync(&pdev->dev);
|
|
|
|
if (ret < 0) {
|
|
|
|
pm_runtime_put_noidle(&pdev->dev);
|
|
|
|
return ret;
|
|
|
|
}
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac)
|
|
|
|
unregister_netdev(cpsw->slaves[1].ndev);
|
2013-04-24 16:48:23 +08:00
|
|
|
unregister_netdev(ndev);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-12-07 08:00:41 +08:00
|
|
|
cpts_release(cpsw->cpts);
|
2016-08-10 07:22:40 +08:00
|
|
|
cpdma_ctlr_destroy(cpsw->dma);
|
2016-11-18 00:40:00 +08:00
|
|
|
cpsw_remove_dt(pdev);
|
2016-07-29 01:50:35 +08:00
|
|
|
pm_runtime_put_sync(&pdev->dev);
|
|
|
|
pm_runtime_disable(&pdev->dev);
|
2012-03-19 04:17:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-02-27 19:19:45 +08:00
|
|
|
#ifdef CONFIG_PM_SLEEP
|
2012-03-19 04:17:54 +08:00
|
|
|
static int cpsw_suspend(struct device *dev)
|
|
|
|
{
|
2018-10-22 04:00:17 +08:00
|
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
2016-08-10 07:22:42 +08:00
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac) {
|
2014-09-12 01:22:38 +08:00
|
|
|
int i;
|
2013-11-15 15:29:16 +08:00
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
for (i = 0; i < cpsw->data.slaves; i++) {
|
|
|
|
if (netif_running(cpsw->slaves[i].ndev))
|
|
|
|
cpsw_ndo_stop(cpsw->slaves[i].ndev);
|
2014-09-12 01:22:38 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (netif_running(ndev))
|
|
|
|
cpsw_ndo_stop(ndev);
|
|
|
|
}
|
2013-11-15 15:29:16 +08:00
|
|
|
|
2013-06-07 02:15:14 +08:00
|
|
|
/* Select sleep pin state */
|
2016-08-10 07:22:38 +08:00
|
|
|
pinctrl_pm_select_sleep_state(dev);
|
2013-06-07 02:15:14 +08:00
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int cpsw_resume(struct device *dev)
|
|
|
|
{
|
2018-10-22 04:00:17 +08:00
|
|
|
struct net_device *ndev = dev_get_drvdata(dev);
|
2017-02-14 20:42:15 +08:00
|
|
|
struct cpsw_common *cpsw = ndev_to_cpsw(ndev);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2013-06-07 02:15:14 +08:00
|
|
|
/* Select default pin state */
|
2016-08-10 07:22:38 +08:00
|
|
|
pinctrl_pm_select_default_state(dev);
|
2013-06-07 02:15:14 +08:00
|
|
|
|
2016-11-30 06:27:03 +08:00
|
|
|
/* shut up ASSERT_RTNL() warning in netif_set_real_num_tx/rx_queues */
|
|
|
|
rtnl_lock();
|
2016-08-10 07:22:42 +08:00
|
|
|
if (cpsw->data.dual_emac) {
|
2014-09-12 01:22:38 +08:00
|
|
|
int i;
|
|
|
|
|
2016-08-10 07:22:42 +08:00
|
|
|
for (i = 0; i < cpsw->data.slaves; i++) {
|
|
|
|
if (netif_running(cpsw->slaves[i].ndev))
|
|
|
|
cpsw_ndo_open(cpsw->slaves[i].ndev);
|
2014-09-12 01:22:38 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (netif_running(ndev))
|
|
|
|
cpsw_ndo_open(ndev);
|
|
|
|
}
|
2016-11-30 06:27:03 +08:00
|
|
|
rtnl_unlock();
|
|
|
|
|
2012-03-19 04:17:54 +08:00
|
|
|
return 0;
|
|
|
|
}
|
2015-02-27 19:19:45 +08:00
|
|
|
#endif
|
2012-03-19 04:17:54 +08:00
|
|
|
|
2015-02-27 19:19:45 +08:00
|
|
|
static SIMPLE_DEV_PM_OPS(cpsw_pm_ops, cpsw_suspend, cpsw_resume);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
static struct platform_driver cpsw_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "cpsw",
|
|
|
|
.pm = &cpsw_pm_ops,
|
2013-09-30 12:25:12 +08:00
|
|
|
.of_match_table = cpsw_of_mtable,
|
2012-03-19 04:17:54 +08:00
|
|
|
},
|
|
|
|
.probe = cpsw_probe,
|
2012-12-03 22:23:45 +08:00
|
|
|
.remove = cpsw_remove,
|
2012-03-19 04:17:54 +08:00
|
|
|
};
|
|
|
|
|
2015-10-23 19:41:12 +08:00
|
|
|
module_platform_driver(cpsw_driver);
|
2012-03-19 04:17:54 +08:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_AUTHOR("Cyril Chemparathy <cyril@ti.com>");
|
|
|
|
MODULE_AUTHOR("Mugunthan V N <mugunthanvnm@ti.com>");
|
|
|
|
MODULE_DESCRIPTION("TI CPSW Ethernet driver");
|