summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/qualcomm
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/ethernet/qualcomm')
-rw-r--r--drivers/net/ethernet/qualcomm/Kconfig24
-rw-r--r--drivers/net/ethernet/qualcomm/Makefile7
-rw-r--r--drivers/net/ethernet/qualcomm/emac/emac-sgmii.c23
-rw-r--r--drivers/net/ethernet/qualcomm/emac/emac.c16
-rw-r--r--drivers/net/ethernet/qualcomm/qca_7k.c30
-rw-r--r--drivers/net/ethernet/qualcomm/qca_7k.h15
-rw-r--r--drivers/net/ethernet/qualcomm/qca_7k_common.c (renamed from drivers/net/ethernet/qualcomm/qca_framing.c)26
-rw-r--r--drivers/net/ethernet/qualcomm/qca_7k_common.h (renamed from drivers/net/ethernet/qualcomm/qca_framing.h)24
-rw-r--r--drivers/net/ethernet/qualcomm/qca_debug.c5
-rw-r--r--drivers/net/ethernet/qualcomm/qca_spi.c51
-rw-r--r--drivers/net/ethernet/qualcomm/qca_spi.h5
-rw-r--r--drivers/net/ethernet/qualcomm/qca_uart.c423
12 files changed, 571 insertions, 78 deletions
diff --git a/drivers/net/ethernet/qualcomm/Kconfig b/drivers/net/ethernet/qualcomm/Kconfig
index d7720bf92d49..877675a27b9f 100644
--- a/drivers/net/ethernet/qualcomm/Kconfig
+++ b/drivers/net/ethernet/qualcomm/Kconfig
@@ -16,7 +16,13 @@ config NET_VENDOR_QUALCOMM
if NET_VENDOR_QUALCOMM
config QCA7000
- tristate "Qualcomm Atheros QCA7000 support"
+ tristate
+ help
+ This enables support for the Qualcomm Atheros QCA7000.
+
+config QCA7000_SPI
+ tristate "Qualcomm Atheros QCA7000 SPI support"
+ select QCA7000
depends on SPI_MASTER && OF
---help---
This SPI protocol driver supports the Qualcomm Atheros QCA7000.
@@ -24,6 +30,22 @@ config QCA7000
To compile this driver as a module, choose M here. The module
will be called qcaspi.
+config QCA7000_UART
+ tristate "Qualcomm Atheros QCA7000 UART support"
+ select QCA7000
+ depends on SERIAL_DEV_BUS && OF
+ ---help---
+ This UART protocol driver supports the Qualcomm Atheros QCA7000.
+
+ Currently the driver assumes these device UART settings:
+ Data bits: 8
+ Parity: None
+ Stop bits: 1
+ Flow control: None
+
+ To compile this driver as a module, choose M here. The module
+ will be called qcauart.
+
config QCOM_EMAC
tristate "Qualcomm Technologies, Inc. EMAC Gigabit Ethernet support"
depends on HAS_DMA && HAS_IOMEM
diff --git a/drivers/net/ethernet/qualcomm/Makefile b/drivers/net/ethernet/qualcomm/Makefile
index aacb0a585c68..92fa7c4da90a 100644
--- a/drivers/net/ethernet/qualcomm/Makefile
+++ b/drivers/net/ethernet/qualcomm/Makefile
@@ -2,7 +2,10 @@
# Makefile for the Qualcomm network device drivers.
#
-obj-$(CONFIG_QCA7000) += qcaspi.o
-qcaspi-objs := qca_spi.o qca_framing.o qca_7k.o qca_debug.o
+obj-$(CONFIG_QCA7000) += qca_7k_common.o
+obj-$(CONFIG_QCA7000_SPI) += qcaspi.o
+qcaspi-objs := qca_7k.o qca_debug.o qca_spi.o
+obj-$(CONFIG_QCA7000_UART) += qcauart.o
+qcauart-objs := qca_uart.o
obj-y += emac/
diff --git a/drivers/net/ethernet/qualcomm/emac/emac-sgmii.c b/drivers/net/ethernet/qualcomm/emac/emac-sgmii.c
index 18c184ee1f3c..29ba37a08372 100644
--- a/drivers/net/ethernet/qualcomm/emac/emac-sgmii.c
+++ b/drivers/net/ethernet/qualcomm/emac/emac-sgmii.c
@@ -297,6 +297,14 @@ static const struct of_device_id emac_sgmii_dt_match[] = {
{}
};
+/* Dummy function for systems without an internal PHY. This avoids having
+ * to check for NULL pointers before calling the functions.
+ */
+static int emac_sgmii_dummy(struct emac_adapter *adpt)
+{
+ return 0;
+}
+
int emac_sgmii_config(struct platform_device *pdev, struct emac_adapter *adpt)
{
struct platform_device *sgmii_pdev = NULL;
@@ -311,8 +319,19 @@ int emac_sgmii_config(struct platform_device *pdev, struct emac_adapter *adpt)
emac_sgmii_acpi_match);
if (!dev) {
- dev_err(&pdev->dev, "cannot find internal phy node\n");
- return -ENODEV;
+ dev_warn(&pdev->dev, "cannot find internal phy node\n");
+ /* There is typically no internal PHY on emulation
+ * systems, so if we can't find the node, assume
+ * we are on an emulation system and stub-out
+ * support for the internal PHY. These systems only
+ * use ACPI.
+ */
+ phy->open = emac_sgmii_dummy;
+ phy->close = emac_sgmii_dummy;
+ phy->link_up = emac_sgmii_dummy;
+ phy->link_down = emac_sgmii_dummy;
+
+ return 0;
}
sgmii_pdev = to_platform_device(dev);
diff --git a/drivers/net/ethernet/qualcomm/emac/emac.c b/drivers/net/ethernet/qualcomm/emac/emac.c
index 98a326faea29..746d94e28470 100644
--- a/drivers/net/ethernet/qualcomm/emac/emac.c
+++ b/drivers/net/ethernet/qualcomm/emac/emac.c
@@ -683,8 +683,6 @@ static int emac_probe(struct platform_device *pdev)
goto err_undo_mdiobus;
}
- emac_mac_reset(adpt);
-
/* set hw features */
netdev->features = NETIF_F_SG | NETIF_F_HW_CSUM | NETIF_F_RXCSUM |
NETIF_F_TSO | NETIF_F_TSO6 | NETIF_F_HW_VLAN_CTAG_RX |
@@ -762,6 +760,19 @@ static int emac_remove(struct platform_device *pdev)
return 0;
}
+static void emac_shutdown(struct platform_device *pdev)
+{
+ struct net_device *netdev = dev_get_drvdata(&pdev->dev);
+ struct emac_adapter *adpt = netdev_priv(netdev);
+ struct emac_sgmii *sgmii = &adpt->phy;
+
+ /* Closing the SGMII turns off its interrupts */
+ sgmii->close(adpt);
+
+ /* Resetting the MAC turns off all DMA and its interrupts */
+ emac_mac_reset(adpt);
+}
+
static struct platform_driver emac_platform_driver = {
.probe = emac_probe,
.remove = emac_remove,
@@ -770,6 +781,7 @@ static struct platform_driver emac_platform_driver = {
.of_match_table = emac_dt_match,
.acpi_match_table = ACPI_PTR(emac_acpi_match),
},
+ .shutdown = emac_shutdown,
};
module_platform_driver(emac_platform_driver);
diff --git a/drivers/net/ethernet/qualcomm/qca_7k.c b/drivers/net/ethernet/qualcomm/qca_7k.c
index f0066fbb44a6..ffe7a16bdfc8 100644
--- a/drivers/net/ethernet/qualcomm/qca_7k.c
+++ b/drivers/net/ethernet/qualcomm/qca_7k.c
@@ -23,11 +23,9 @@
* kernel-based SPI device.
*/
-#include <linux/init.h>
-#include <linux/module.h>
-#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
#include <linux/spi/spi.h>
-#include <linux/version.h>
#include "qca_7k.h"
@@ -123,27 +121,3 @@ qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value)
return ret;
}
-
-int
-qcaspi_tx_cmd(struct qcaspi *qca, u16 cmd)
-{
- __be16 tx_data;
- struct spi_message *msg = &qca->spi_msg1;
- struct spi_transfer *transfer = &qca->spi_xfer1;
- int ret;
-
- tx_data = cpu_to_be16(cmd);
- transfer->len = sizeof(tx_data);
- transfer->tx_buf = &tx_data;
- transfer->rx_buf = NULL;
-
- ret = spi_sync(qca->spi_dev, msg);
-
- if (!ret)
- ret = msg->status;
-
- if (ret)
- qcaspi_spi_error(qca);
-
- return ret;
-}
diff --git a/drivers/net/ethernet/qualcomm/qca_7k.h b/drivers/net/ethernet/qualcomm/qca_7k.h
index 1cad851ee507..27124c2bb77a 100644
--- a/drivers/net/ethernet/qualcomm/qca_7k.h
+++ b/drivers/net/ethernet/qualcomm/qca_7k.h
@@ -54,19 +54,18 @@
#define SPI_REG_ACTION_CTRL 0x1B00
/* SPI_CONFIG register definition; */
-#define QCASPI_SLAVE_RESET_BIT (1 << 6)
+#define QCASPI_SLAVE_RESET_BIT BIT(6)
/* INTR_CAUSE/ENABLE register definition. */
-#define SPI_INT_WRBUF_BELOW_WM (1 << 10)
-#define SPI_INT_CPU_ON (1 << 6)
-#define SPI_INT_ADDR_ERR (1 << 3)
-#define SPI_INT_WRBUF_ERR (1 << 2)
-#define SPI_INT_RDBUF_ERR (1 << 1)
-#define SPI_INT_PKT_AVLBL (1 << 0)
+#define SPI_INT_WRBUF_BELOW_WM BIT(10)
+#define SPI_INT_CPU_ON BIT(6)
+#define SPI_INT_ADDR_ERR BIT(3)
+#define SPI_INT_WRBUF_ERR BIT(2)
+#define SPI_INT_RDBUF_ERR BIT(1)
+#define SPI_INT_PKT_AVLBL BIT(0)
void qcaspi_spi_error(struct qcaspi *qca);
int qcaspi_read_register(struct qcaspi *qca, u16 reg, u16 *result);
int qcaspi_write_register(struct qcaspi *qca, u16 reg, u16 value);
-int qcaspi_tx_cmd(struct qcaspi *qca, u16 cmd);
#endif /* _QCA_7K_H */
diff --git a/drivers/net/ethernet/qualcomm/qca_framing.c b/drivers/net/ethernet/qualcomm/qca_7k_common.c
index faa924c85e29..6b511f05df61 100644
--- a/drivers/net/ethernet/qualcomm/qca_framing.c
+++ b/drivers/net/ethernet/qualcomm/qca_7k_common.c
@@ -21,9 +21,11 @@
* by an atheros frame while transmitted over a serial channel;
*/
+#include <linux/init.h>
#include <linux/kernel.h>
+#include <linux/module.h>
-#include "qca_framing.h"
+#include "qca_7k_common.h"
u16
qcafrm_create_header(u8 *buf, u16 length)
@@ -46,6 +48,7 @@ qcafrm_create_header(u8 *buf, u16 length)
return QCAFRM_HEADER_LEN;
}
+EXPORT_SYMBOL_GPL(qcafrm_create_header);
u16
qcafrm_create_footer(u8 *buf)
@@ -57,6 +60,7 @@ qcafrm_create_footer(u8 *buf)
buf[1] = 0x55;
return QCAFRM_FOOTER_LEN;
}
+EXPORT_SYMBOL_GPL(qcafrm_create_footer);
/* Gather received bytes and try to extract a full ethernet frame by
* following a simple state machine.
@@ -83,7 +87,7 @@ qcafrm_fsm_decode(struct qcafrm_handle *handle, u8 *buf, u16 buf_len, u8 recv_by
if (recv_byte != 0x00) {
/* first two bytes of length must be 0 */
- handle->state = QCAFRM_HW_LEN0;
+ handle->state = handle->init;
}
break;
case QCAFRM_HW_LEN2:
@@ -97,7 +101,7 @@ qcafrm_fsm_decode(struct qcafrm_handle *handle, u8 *buf, u16 buf_len, u8 recv_by
case QCAFRM_WAIT_AA4:
if (recv_byte != 0xAA) {
ret = QCAFRM_NOHEAD;
- handle->state = QCAFRM_HW_LEN0;
+ handle->state = handle->init;
} else {
handle->state--;
}
@@ -117,9 +121,9 @@ qcafrm_fsm_decode(struct qcafrm_handle *handle, u8 *buf, u16 buf_len, u8 recv_by
break;
case QCAFRM_WAIT_RSVD_BYTE2:
len = handle->offset;
- if (len > buf_len || len < QCAFRM_ETHMINLEN) {
+ if (len > buf_len || len < QCAFRM_MIN_LEN) {
ret = QCAFRM_INVLEN;
- handle->state = QCAFRM_HW_LEN0;
+ handle->state = handle->init;
} else {
handle->state = (enum qcafrm_state)(len + 1);
/* Remaining number of bytes. */
@@ -135,7 +139,7 @@ qcafrm_fsm_decode(struct qcafrm_handle *handle, u8 *buf, u16 buf_len, u8 recv_by
case QCAFRM_WAIT_551:
if (recv_byte != 0x55) {
ret = QCAFRM_NOTAIL;
- handle->state = QCAFRM_HW_LEN0;
+ handle->state = handle->init;
} else {
handle->state = QCAFRM_WAIT_552;
}
@@ -143,14 +147,20 @@ qcafrm_fsm_decode(struct qcafrm_handle *handle, u8 *buf, u16 buf_len, u8 recv_by
case QCAFRM_WAIT_552:
if (recv_byte != 0x55) {
ret = QCAFRM_NOTAIL;
- handle->state = QCAFRM_HW_LEN0;
+ handle->state = handle->init;
} else {
ret = handle->offset;
/* Frame is fully received. */
- handle->state = QCAFRM_HW_LEN0;
+ handle->state = handle->init;
}
break;
}
return ret;
}
+EXPORT_SYMBOL_GPL(qcafrm_fsm_decode);
+
+MODULE_DESCRIPTION("Qualcomm Atheros QCA7000 common");
+MODULE_AUTHOR("Qualcomm Atheros Communications");
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/qualcomm/qca_framing.h b/drivers/net/ethernet/qualcomm/qca_7k_common.h
index d5e795dcdf47..928554f11e35 100644
--- a/drivers/net/ethernet/qualcomm/qca_framing.h
+++ b/drivers/net/ethernet/qualcomm/qca_7k_common.h
@@ -44,12 +44,12 @@
#define QCAFRM_INVFRAME (QCAFRM_ERR_BASE - 4)
/* Min/Max Ethernet MTU: 46/1500 */
-#define QCAFRM_ETHMINMTU (ETH_ZLEN - ETH_HLEN)
-#define QCAFRM_ETHMAXMTU ETH_DATA_LEN
+#define QCAFRM_MIN_MTU (ETH_ZLEN - ETH_HLEN)
+#define QCAFRM_MAX_MTU ETH_DATA_LEN
/* Min/Max frame lengths */
-#define QCAFRM_ETHMINLEN (QCAFRM_ETHMINMTU + ETH_HLEN)
-#define QCAFRM_ETHMAXLEN (QCAFRM_ETHMAXMTU + VLAN_ETH_HLEN)
+#define QCAFRM_MIN_LEN (QCAFRM_MIN_MTU + ETH_HLEN)
+#define QCAFRM_MAX_LEN (QCAFRM_MAX_MTU + VLAN_ETH_HLEN)
/* QCA7K header len */
#define QCAFRM_HEADER_LEN 8
@@ -61,6 +61,7 @@
#define QCAFRM_ERR_BASE -1000
enum qcafrm_state {
+ /* HW length is only available on SPI */
QCAFRM_HW_LEN0 = 0x8000,
QCAFRM_HW_LEN1 = QCAFRM_HW_LEN0 - 1,
QCAFRM_HW_LEN2 = QCAFRM_HW_LEN1 - 1,
@@ -101,9 +102,11 @@ enum qcafrm_state {
struct qcafrm_handle {
/* Current decoding state */
enum qcafrm_state state;
+ /* Initial state depends on connection type */
+ enum qcafrm_state init;
/* Offset in buffer (borrowed for length too) */
- s16 offset;
+ u16 offset;
/* Frame length as kept by this module */
u16 len;
@@ -113,9 +116,16 @@ u16 qcafrm_create_header(u8 *buf, u16 len);
u16 qcafrm_create_footer(u8 *buf);
-static inline void qcafrm_fsm_init(struct qcafrm_handle *handle)
+static inline void qcafrm_fsm_init_spi(struct qcafrm_handle *handle)
{
- handle->state = QCAFRM_HW_LEN0;
+ handle->init = QCAFRM_HW_LEN0;
+ handle->state = handle->init;
+}
+
+static inline void qcafrm_fsm_init_uart(struct qcafrm_handle *handle)
+{
+ handle->init = QCAFRM_WAIT_AA1;
+ handle->state = handle->init;
}
/* Gather received bytes and try to extract a full Ethernet frame
diff --git a/drivers/net/ethernet/qualcomm/qca_debug.c b/drivers/net/ethernet/qualcomm/qca_debug.c
index d145df98feff..92b6be9c4429 100644
--- a/drivers/net/ethernet/qualcomm/qca_debug.c
+++ b/drivers/net/ethernet/qualcomm/qca_debug.c
@@ -275,6 +275,7 @@ qcaspi_get_ringparam(struct net_device *dev, struct ethtool_ringparam *ring)
static int
qcaspi_set_ringparam(struct net_device *dev, struct ethtool_ringparam *ring)
{
+ const struct net_device_ops *ops = dev->netdev_ops;
struct qcaspi *qca = netdev_priv(dev);
if ((ring->rx_pending) ||
@@ -283,13 +284,13 @@ qcaspi_set_ringparam(struct net_device *dev, struct ethtool_ringparam *ring)
return -EINVAL;
if (netif_running(dev))
- qcaspi_netdev_close(dev);
+ ops->ndo_stop(dev);
qca->txr.count = max_t(u32, ring->tx_pending, TX_RING_MIN_LEN);
qca->txr.count = min_t(u16, qca->txr.count, TX_RING_MAX_LEN);
if (netif_running(dev))
- qcaspi_netdev_open(dev);
+ ops->ndo_open(dev);
return 0;
}
diff --git a/drivers/net/ethernet/qualcomm/qca_spi.c b/drivers/net/ethernet/qualcomm/qca_spi.c
index 24ca7df15d07..9c236298fe21 100644
--- a/drivers/net/ethernet/qualcomm/qca_spi.c
+++ b/drivers/net/ethernet/qualcomm/qca_spi.c
@@ -43,8 +43,8 @@
#include <linux/types.h>
#include "qca_7k.h"
+#include "qca_7k_common.h"
#include "qca_debug.h"
-#include "qca_framing.h"
#include "qca_spi.h"
#define MAX_DMA_BURST_LEN 5000
@@ -69,7 +69,6 @@ static int qcaspi_pluggable = QCASPI_PLUGGABLE_MIN;
module_param(qcaspi_pluggable, int, 0);
MODULE_PARM_DESC(qcaspi_pluggable, "Pluggable SPI connection (yes/no).");
-#define QCASPI_MTU QCAFRM_ETHMAXMTU
#define QCASPI_TX_TIMEOUT (1 * HZ)
#define QCASPI_QCA7K_REBOOT_TIME_MS 1000
@@ -193,6 +192,30 @@ qcaspi_read_legacy(struct qcaspi *qca, u8 *dst, u32 len)
}
static int
+qcaspi_tx_cmd(struct qcaspi *qca, u16 cmd)
+{
+ __be16 tx_data;
+ struct spi_message *msg = &qca->spi_msg1;
+ struct spi_transfer *transfer = &qca->spi_xfer1;
+ int ret;
+
+ tx_data = cpu_to_be16(cmd);
+ transfer->len = sizeof(tx_data);
+ transfer->tx_buf = &tx_data;
+ transfer->rx_buf = NULL;
+
+ ret = spi_sync(qca->spi_dev, msg);
+
+ if (!ret)
+ ret = msg->status;
+
+ if (ret)
+ qcaspi_spi_error(qca);
+
+ return ret;
+}
+
+static int
qcaspi_tx_frame(struct qcaspi *qca, struct sk_buff *skb)
{
u32 count;
@@ -403,7 +426,7 @@ qcaspi_tx_ring_has_space(struct tx_ring *txr)
if (txr->skb[txr->tail])
return 0;
- return (txr->size + QCAFRM_ETHMAXLEN < QCASPI_HW_BUF_LEN) ? 1 : 0;
+ return (txr->size + QCAFRM_MAX_LEN < QCASPI_HW_BUF_LEN) ? 1 : 0;
}
/* Flush the tx ring. This function is only safe to
@@ -603,7 +626,7 @@ qcaspi_intr_handler(int irq, void *data)
return IRQ_HANDLED;
}
-int
+static int
qcaspi_netdev_open(struct net_device *dev)
{
struct qcaspi *qca = netdev_priv(dev);
@@ -615,7 +638,7 @@ qcaspi_netdev_open(struct net_device *dev)
qca->intr_req = 1;
qca->intr_svc = 0;
qca->sync = QCASPI_SYNC_UNKNOWN;
- qcafrm_fsm_init(&qca->frm_handle);
+ qcafrm_fsm_init_spi(&qca->frm_handle);
qca->spi_thread = kthread_run((void *)qcaspi_spi_thread,
qca, "%s", dev->name);
@@ -640,7 +663,7 @@ qcaspi_netdev_open(struct net_device *dev)
return 0;
}
-int
+static int
qcaspi_netdev_close(struct net_device *dev)
{
struct qcaspi *qca = netdev_priv(dev);
@@ -667,8 +690,8 @@ qcaspi_netdev_xmit(struct sk_buff *skb, struct net_device *dev)
struct sk_buff *tskb;
u8 pad_len = 0;
- if (skb->len < QCAFRM_ETHMINLEN)
- pad_len = QCAFRM_ETHMINLEN - skb->len;
+ if (skb->len < QCAFRM_MIN_LEN)
+ pad_len = QCAFRM_MIN_LEN - skb->len;
if (qca->txr.skb[qca->txr.tail]) {
netdev_warn(qca->net_dev, "queue was unexpectedly full!\n");
@@ -696,8 +719,7 @@ qcaspi_netdev_xmit(struct sk_buff *skb, struct net_device *dev)
qcafrm_create_header(ptmp, frame_len);
if (pad_len) {
- ptmp = skb_put(skb, pad_len);
- memset(ptmp, 0, pad_len);
+ ptmp = skb_put_zero(skb, pad_len);
}
ptmp = skb_put(skb, QCAFRM_FOOTER_LEN);
@@ -746,7 +768,7 @@ qcaspi_netdev_init(struct net_device *dev)
{
struct qcaspi *qca = netdev_priv(dev);
- dev->mtu = QCASPI_MTU;
+ dev->mtu = QCAFRM_MAX_MTU;
dev->type = ARPHRD_ETHER;
qca->clkspeed = qcaspi_clkspeed;
qca->burst_len = qcaspi_burst_len;
@@ -805,8 +827,8 @@ qcaspi_netdev_setup(struct net_device *dev)
dev->tx_queue_len = 100;
/* MTU range: 46 - 1500 */
- dev->min_mtu = QCAFRM_ETHMINMTU;
- dev->max_mtu = QCAFRM_ETHMAXMTU;
+ dev->min_mtu = QCAFRM_MIN_MTU;
+ dev->max_mtu = QCAFRM_MAX_MTU;
qca = netdev_priv(dev);
memset(qca, 0, sizeof(struct qcaspi));
@@ -894,6 +916,7 @@ qca_spi_probe(struct spi_device *spi)
return -ENOMEM;
qcaspi_netdev_setup(qcaspi_devs);
+ SET_NETDEV_DEV(qcaspi_devs, &spi->dev);
qca = netdev_priv(qcaspi_devs);
if (!qca) {
@@ -975,7 +998,7 @@ static struct spi_driver qca_spi_driver = {
};
module_spi_driver(qca_spi_driver);
-MODULE_DESCRIPTION("Qualcomm Atheros SPI Driver");
+MODULE_DESCRIPTION("Qualcomm Atheros QCA7000 SPI Driver");
MODULE_AUTHOR("Qualcomm Atheros Communications");
MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/net/ethernet/qualcomm/qca_spi.h b/drivers/net/ethernet/qualcomm/qca_spi.h
index 6e31a0e744a4..fc4beb1b32d1 100644
--- a/drivers/net/ethernet/qualcomm/qca_spi.h
+++ b/drivers/net/ethernet/qualcomm/qca_spi.h
@@ -32,7 +32,7 @@
#include <linux/spi/spi.h>
#include <linux/types.h>
-#include "qca_framing.h"
+#include "qca_7k_common.h"
#define QCASPI_DRV_VERSION "0.2.7-i"
#define QCASPI_DRV_NAME "qcaspi"
@@ -108,7 +108,4 @@ struct qcaspi {
u16 burst_len;
};
-int qcaspi_netdev_open(struct net_device *dev);
-int qcaspi_netdev_close(struct net_device *dev);
-
#endif /* _QCA_SPI_H */
diff --git a/drivers/net/ethernet/qualcomm/qca_uart.c b/drivers/net/ethernet/qualcomm/qca_uart.c
new file mode 100644
index 000000000000..db6068cd7a1f
--- /dev/null
+++ b/drivers/net/ethernet/qualcomm/qca_uart.c
@@ -0,0 +1,423 @@
+/*
+ * Copyright (c) 2011, 2012, Qualcomm Atheros Communications Inc.
+ * Copyright (c) 2017, I2SE GmbH
+ *
+ * Permission to use, copy, modify, and/or distribute this software
+ * for any purpose with or without fee is hereby granted, provided
+ * that the above copyright notice and this permission notice appear
+ * in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
+ * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+ * THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
+ * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/* This module implements the Qualcomm Atheros UART protocol for
+ * kernel-based UART device; it is essentially an Ethernet-to-UART
+ * serial converter;
+ */
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_net.h>
+#include <linux/sched.h>
+#include <linux/serdev.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+#include "qca_7k_common.h"
+
+#define QCAUART_DRV_VERSION "0.1.0"
+#define QCAUART_DRV_NAME "qcauart"
+#define QCAUART_TX_TIMEOUT (1 * HZ)
+
+struct qcauart {
+ struct net_device *net_dev;
+ spinlock_t lock; /* transmit lock */
+ struct work_struct tx_work; /* Flushes transmit buffer */
+
+ struct serdev_device *serdev;
+ struct qcafrm_handle frm_handle;
+ struct sk_buff *rx_skb;
+
+ unsigned char *tx_head; /* pointer to next XMIT byte */
+ int tx_left; /* bytes left in XMIT queue */
+ unsigned char *tx_buffer;
+};
+
+static int
+qca_tty_receive(struct serdev_device *serdev, const unsigned char *data,
+ size_t count)
+{
+ struct qcauart *qca = serdev_device_get_drvdata(serdev);
+ struct net_device *netdev = qca->net_dev;
+ struct net_device_stats *n_stats = &netdev->stats;
+ size_t i;
+
+ if (!qca->rx_skb) {
+ qca->rx_skb = netdev_alloc_skb_ip_align(netdev,
+ netdev->mtu +
+ VLAN_ETH_HLEN);
+ if (!qca->rx_skb) {
+ n_stats->rx_errors++;
+ n_stats->rx_dropped++;
+ return 0;
+ }
+ }
+
+ for (i = 0; i < count; i++) {
+ s32 retcode;
+
+ retcode = qcafrm_fsm_decode(&qca->frm_handle,
+ qca->rx_skb->data,
+ skb_tailroom(qca->rx_skb),
+ data[i]);
+
+ switch (retcode) {
+ case QCAFRM_GATHER:
+ case QCAFRM_NOHEAD:
+ break;
+ case QCAFRM_NOTAIL:
+ netdev_dbg(netdev, "recv: no RX tail\n");
+ n_stats->rx_errors++;
+ n_stats->rx_dropped++;
+ break;
+ case QCAFRM_INVLEN:
+ netdev_dbg(netdev, "recv: invalid RX length\n");
+ n_stats->rx_errors++;
+ n_stats->rx_dropped++;
+ break;
+ default:
+ n_stats->rx_packets++;
+ n_stats->rx_bytes += retcode;
+ skb_put(qca->rx_skb, retcode);
+ qca->rx_skb->protocol = eth_type_trans(
+ qca->rx_skb, qca->rx_skb->dev);
+ qca->rx_skb->ip_summed = CHECKSUM_UNNECESSARY;
+ netif_rx_ni(qca->rx_skb);
+ qca->rx_skb = netdev_alloc_skb_ip_align(netdev,
+ netdev->mtu +
+ VLAN_ETH_HLEN);
+ if (!qca->rx_skb) {
+ netdev_dbg(netdev, "recv: out of RX resources\n");
+ n_stats->rx_errors++;
+ return i;
+ }
+ }
+ }
+
+ return i;
+}
+
+/* Write out any remaining transmit buffer. Scheduled when tty is writable */
+static void qcauart_transmit(struct work_struct *work)
+{
+ struct qcauart *qca = container_of(work, struct qcauart, tx_work);
+ struct net_device_stats *n_stats = &qca->net_dev->stats;
+ int written;
+
+ spin_lock_bh(&qca->lock);
+
+ /* First make sure we're connected. */
+ if (!netif_running(qca->net_dev)) {
+ spin_unlock_bh(&qca->lock);
+ return;
+ }
+
+ if (qca->tx_left <= 0) {
+ /* Now serial buffer is almost free & we can start
+ * transmission of another packet
+ */
+ n_stats->tx_packets++;
+ spin_unlock_bh(&qca->lock);
+ netif_wake_queue(qca->net_dev);
+ return;
+ }
+
+ written = serdev_device_write_buf(qca->serdev, qca->tx_head,
+ qca->tx_left);
+ if (written > 0) {
+ qca->tx_left -= written;
+ qca->tx_head += written;
+ }
+ spin_unlock_bh(&qca->lock);
+}
+
+/* Called by the driver when there's room for more data.
+ * Schedule the transmit.
+ */
+static void qca_tty_wakeup(struct serdev_device *serdev)
+{
+ struct qcauart *qca = serdev_device_get_drvdata(serdev);
+
+ schedule_work(&qca->tx_work);
+}
+
+static struct serdev_device_ops qca_serdev_ops = {
+ .receive_buf = qca_tty_receive,
+ .write_wakeup = qca_tty_wakeup,
+};
+
+static int qcauart_netdev_open(struct net_device *dev)
+{
+ struct qcauart *qca = netdev_priv(dev);
+
+ netif_start_queue(qca->net_dev);
+
+ return 0;
+}
+
+static int qcauart_netdev_close(struct net_device *dev)
+{
+ struct qcauart *qca = netdev_priv(dev);
+
+ netif_stop_queue(dev);
+ flush_work(&qca->tx_work);
+
+ spin_lock_bh(&qca->lock);
+ qca->tx_left = 0;
+ spin_unlock_bh(&qca->lock);
+
+ return 0;
+}
+
+static netdev_tx_t
+qcauart_netdev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *n_stats = &dev->stats;
+ struct qcauart *qca = netdev_priv(dev);
+ u8 pad_len = 0;
+ int written;
+ u8 *pos;
+
+ spin_lock(&qca->lock);
+
+ WARN_ON(qca->tx_left);
+
+ if (!netif_running(dev)) {
+ spin_unlock(&qca->lock);
+ netdev_warn(qca->net_dev, "xmit: iface is down\n");
+ goto out;
+ }
+
+ pos = qca->tx_buffer;
+
+ if (skb->len < QCAFRM_MIN_LEN)
+ pad_len = QCAFRM_MIN_LEN - skb->len;
+
+ pos += qcafrm_create_header(pos, skb->len + pad_len);
+
+ memcpy(pos, skb->data, skb->len);
+ pos += skb->len;
+
+ if (pad_len) {
+ memset(pos, 0, pad_len);
+ pos += pad_len;
+ }
+
+ pos += qcafrm_create_footer(pos);
+
+ netif_stop_queue(qca->net_dev);
+
+ written = serdev_device_write_buf(qca->serdev, qca->tx_buffer,
+ pos - qca->tx_buffer);
+ if (written > 0) {
+ qca->tx_left = (pos - qca->tx_buffer) - written;
+ qca->tx_head = qca->tx_buffer + written;
+ n_stats->tx_bytes += written;
+ }
+ spin_unlock(&qca->lock);
+
+ netif_trans_update(dev);
+out:
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+}
+
+static void qcauart_netdev_tx_timeout(struct net_device *dev)
+{
+ struct qcauart *qca = netdev_priv(dev);
+
+ netdev_info(qca->net_dev, "Transmit timeout at %ld, latency %ld\n",
+ jiffies, dev_trans_start(dev));
+ dev->stats.tx_errors++;
+ dev->stats.tx_dropped++;
+}
+
+static int qcauart_netdev_init(struct net_device *dev)
+{
+ struct qcauart *qca = netdev_priv(dev);
+ size_t len;
+
+ /* Finish setting up the device info. */
+ dev->mtu = QCAFRM_MAX_MTU;
+ dev->type = ARPHRD_ETHER;
+
+ len = QCAFRM_HEADER_LEN + QCAFRM_MAX_LEN + QCAFRM_FOOTER_LEN;
+ qca->tx_buffer = devm_kmalloc(&qca->serdev->dev, len, GFP_KERNEL);
+ if (!qca->tx_buffer)
+ return -ENOMEM;
+
+ qca->rx_skb = netdev_alloc_skb_ip_align(qca->net_dev,
+ qca->net_dev->mtu +
+ VLAN_ETH_HLEN);
+ if (!qca->rx_skb)
+ return -ENOBUFS;
+
+ return 0;
+}
+
+static void qcauart_netdev_uninit(struct net_device *dev)
+{
+ struct qcauart *qca = netdev_priv(dev);
+
+ if (qca->rx_skb)
+ dev_kfree_skb(qca->rx_skb);
+}
+
+static const struct net_device_ops qcauart_netdev_ops = {
+ .ndo_init = qcauart_netdev_init,
+ .ndo_uninit = qcauart_netdev_uninit,
+ .ndo_open = qcauart_netdev_open,
+ .ndo_stop = qcauart_netdev_close,
+ .ndo_start_xmit = qcauart_netdev_xmit,
+ .ndo_set_mac_address = eth_mac_addr,
+ .ndo_tx_timeout = qcauart_netdev_tx_timeout,
+ .ndo_validate_addr = eth_validate_addr,
+};
+
+static void qcauart_netdev_setup(struct net_device *dev)
+{
+ dev->netdev_ops = &qcauart_netdev_ops;
+ dev->watchdog_timeo = QCAUART_TX_TIMEOUT;
+ dev->priv_flags &= ~IFF_TX_SKB_SHARING;
+ dev->tx_queue_len = 100;
+
+ /* MTU range: 46 - 1500 */
+ dev->min_mtu = QCAFRM_MIN_MTU;
+ dev->max_mtu = QCAFRM_MAX_MTU;
+}
+
+static const struct of_device_id qca_uart_of_match[] = {
+ {
+ .compatible = "qca,qca7000",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, qca_uart_of_match);
+
+static int qca_uart_probe(struct serdev_device *serdev)
+{
+ struct net_device *qcauart_dev = alloc_etherdev(sizeof(struct qcauart));
+ struct qcauart *qca;
+ const char *mac;
+ u32 speed = 115200;
+ int ret;
+
+ if (!qcauart_dev)
+ return -ENOMEM;
+
+ qcauart_netdev_setup(qcauart_dev);
+ SET_NETDEV_DEV(qcauart_dev, &serdev->dev);
+
+ qca = netdev_priv(qcauart_dev);
+ if (!qca) {
+ pr_err("qca_uart: Fail to retrieve private structure\n");
+ ret = -ENOMEM;
+ goto free;
+ }
+ qca->net_dev = qcauart_dev;
+ qca->serdev = serdev;
+ qcafrm_fsm_init_uart(&qca->frm_handle);
+
+ spin_lock_init(&qca->lock);
+ INIT_WORK(&qca->tx_work, qcauart_transmit);
+
+ of_property_read_u32(serdev->dev.of_node, "current-speed", &speed);
+
+ mac = of_get_mac_address(serdev->dev.of_node);
+
+ if (mac)
+ ether_addr_copy(qca->net_dev->dev_addr, mac);
+
+ if (!is_valid_ether_addr(qca->net_dev->dev_addr)) {
+ eth_hw_addr_random(qca->net_dev);
+ dev_info(&serdev->dev, "Using random MAC address: %pM\n",
+ qca->net_dev->dev_addr);
+ }
+
+ netif_carrier_on(qca->net_dev);
+ serdev_device_set_drvdata(serdev, qca);
+ serdev_device_set_client_ops(serdev, &qca_serdev_ops);
+
+ ret = serdev_device_open(serdev);
+ if (ret) {
+ dev_err(&serdev->dev, "Unable to open device %s\n",
+ qcauart_dev->name);
+ goto free;
+ }
+
+ speed = serdev_device_set_baudrate(serdev, speed);
+ dev_info(&serdev->dev, "Using baudrate: %u\n", speed);
+
+ serdev_device_set_flow_control(serdev, false);
+
+ ret = register_netdev(qcauart_dev);
+ if (ret) {
+ dev_err(&serdev->dev, "Unable to register net device %s\n",
+ qcauart_dev->name);
+ serdev_device_close(serdev);
+ cancel_work_sync(&qca->tx_work);
+ goto free;
+ }
+
+ return 0;
+
+free:
+ free_netdev(qcauart_dev);
+ return ret;
+}
+
+static void qca_uart_remove(struct serdev_device *serdev)
+{
+ struct qcauart *qca = serdev_device_get_drvdata(serdev);
+
+ unregister_netdev(qca->net_dev);
+
+ /* Flush any pending characters in the driver. */
+ serdev_device_close(serdev);
+ cancel_work_sync(&qca->tx_work);
+
+ free_netdev(qca->net_dev);
+}
+
+static struct serdev_device_driver qca_uart_driver = {
+ .probe = qca_uart_probe,
+ .remove = qca_uart_remove,
+ .driver = {
+ .name = QCAUART_DRV_NAME,
+ .of_match_table = of_match_ptr(qca_uart_of_match),
+ },
+};
+
+module_serdev_device_driver(qca_uart_driver);
+
+MODULE_DESCRIPTION("Qualcomm Atheros QCA7000 UART Driver");
+MODULE_AUTHOR("Qualcomm Atheros Communications");
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_VERSION(QCAUART_DRV_VERSION);