diff options
Diffstat (limited to 'drivers/net/ethernet/qualcomm')
-rw-r--r-- | drivers/net/ethernet/qualcomm/Kconfig | 24 | ||||
-rw-r--r-- | drivers/net/ethernet/qualcomm/Makefile | 7 | ||||
-rw-r--r-- | drivers/net/ethernet/qualcomm/emac/emac-sgmii.c | 23 | ||||
-rw-r--r-- | drivers/net/ethernet/qualcomm/emac/emac.c | 16 | ||||
-rw-r--r-- | drivers/net/ethernet/qualcomm/qca_7k.c | 30 | ||||
-rw-r--r-- | drivers/net/ethernet/qualcomm/qca_7k.h | 15 | ||||
-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.c | 5 | ||||
-rw-r--r-- | drivers/net/ethernet/qualcomm/qca_spi.c | 51 | ||||
-rw-r--r-- | drivers/net/ethernet/qualcomm/qca_spi.h | 5 | ||||
-rw-r--r-- | drivers/net/ethernet/qualcomm/qca_uart.c | 423 |
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); |