Merge branch 'net-qualcomm-add-QCA7000-UART-driver'

Stefan Wahren says:

====================
net: qualcomm: add QCA7000 UART driver

The Qualcomm QCA7000 HomePlug GreenPHY supports two interfaces:
UART and SPI. This patch series adds the missing support for UART.

This driver based on the Qualcomm code [1], but contains some changes:
* use random MAC address per default
* use net_device_stats from device
* share frame decoding between SPI and UART driver
* improve error handling
* reimplement tty_wakeup with work queue (based on slcan)
* use new serial device bus instead of ldisc

The patches 1 - 3 are just for clean up and are not related to
the UART support. Patch 4 adds SET_NETDEV_DEV() to qca_spi.
Patches 5 - 16 prepare the existing QCA7000 code for UART support.
The last patch contains the new driver.

The code itself has been tested on a Freescale i.MX28 board and
a Raspberry Pi Zero.

Changes in v8:
  * add necessary header includes to qca_7k.c in order to reflect
    dependencies

Changes in v7:
  * fix race between tx workqueue and device deregistration (reported by Lino)
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2017-05-30 13:57:33 -04:00
commit 9711164d03
13 changed files with 632 additions and 119 deletions

View File

@ -0,0 +1,88 @@
* Qualcomm QCA7000
The QCA7000 is a serial-to-powerline bridge with a host interface which could
be configured either as SPI or UART slave. This configuration is done by
the QCA7000 firmware.
(a) Ethernet over SPI
In order to use the QCA7000 as SPI device it must be defined as a child of a
SPI master in the device tree.
Required properties:
- compatible : Should be "qca,qca7000"
- reg : Should specify the SPI chip select
- interrupts : The first cell should specify the index of the source
interrupt and the second cell should specify the trigger
type as rising edge
- spi-cpha : Must be set
- spi-cpol : Must be set
Optional properties:
- interrupt-parent : Specify the pHandle of the source interrupt
- spi-max-frequency : Maximum frequency of the SPI bus the chip can operate at.
Numbers smaller than 1000000 or greater than 16000000
are invalid. Missing the property will set the SPI
frequency to 8000000 Hertz.
- local-mac-address : see ./ethernet.txt
- qca,legacy-mode : Set the SPI data transfer of the QCA7000 to legacy mode.
In this mode the SPI master must toggle the chip select
between each data word. In burst mode these gaps aren't
necessary, which is faster. This setting depends on how
the QCA7000 is setup via GPIO pin strapping. If the
property is missing the driver defaults to burst mode.
SPI Example:
/* Freescale i.MX28 SPI master*/
ssp2: spi@80014000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx28-spi";
pinctrl-names = "default";
pinctrl-0 = <&spi2_pins_a>;
status = "okay";
qca7000: ethernet@0 {
compatible = "qca,qca7000";
reg = <0x0>;
interrupt-parent = <&gpio3>; /* GPIO Bank 3 */
interrupts = <25 0x1>; /* Index: 25, rising edge */
spi-cpha; /* SPI mode: CPHA=1 */
spi-cpol; /* SPI mode: CPOL=1 */
spi-max-frequency = <8000000>; /* freq: 8 MHz */
local-mac-address = [ A0 B0 C0 D0 E0 F0 ];
};
};
(b) Ethernet over UART
In order to use the QCA7000 as UART slave it must be defined as a child of a
UART master in the device tree. It is possible to preconfigure the UART
settings of the QCA7000 firmware, but it's not possible to change them during
runtime.
Required properties:
- compatible : Should be "qca,qca7000"
Optional properties:
- local-mac-address : see ./ethernet.txt
- current-speed : current baud rate of QCA7000 which defaults to 115200
if absent, see also ../serial/slave-device.txt
UART Example:
/* Freescale i.MX28 UART */
auart0: serial@8006a000 {
compatible = "fsl,imx28-auart", "fsl,imx23-auart";
reg = <0x8006a000 0x2000>;
pinctrl-names = "default";
pinctrl-0 = <&auart0_2pins_a>;
status = "okay";
qca7000: ethernet {
compatible = "qca,qca7000";
local-mac-address = [ A0 B0 C0 D0 E0 F0 ];
current-speed = <38400>;
};
};

View File

@ -1,47 +0,0 @@
* Qualcomm QCA7000 (Ethernet over SPI protocol)
Note: The QCA7000 is useable as a SPI device. In this case it must be defined
as a child of a SPI master in the device tree.
Required properties:
- compatible : Should be "qca,qca7000"
- reg : Should specify the SPI chip select
- interrupts : The first cell should specify the index of the source interrupt
and the second cell should specify the trigger type as rising edge
- spi-cpha : Must be set
- spi-cpol: Must be set
Optional properties:
- interrupt-parent : Specify the pHandle of the source interrupt
- spi-max-frequency : Maximum frequency of the SPI bus the chip can operate at.
Numbers smaller than 1000000 or greater than 16000000 are invalid. Missing
the property will set the SPI frequency to 8000000 Hertz.
- local-mac-address: 6 bytes, MAC address
- qca,legacy-mode : Set the SPI data transfer of the QCA7000 to legacy mode.
In this mode the SPI master must toggle the chip select between each data
word. In burst mode these gaps aren't necessary, which is faster.
This setting depends on how the QCA7000 is setup via GPIO pin strapping.
If the property is missing the driver defaults to burst mode.
Example:
/* Freescale i.MX28 SPI master*/
ssp2: spi@80014000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx28-spi";
pinctrl-names = "default";
pinctrl-0 = <&spi2_pins_a>;
status = "okay";
qca7000: ethernet@0 {
compatible = "qca,qca7000";
reg = <0x0>;
interrupt-parent = <&gpio3>; /* GPIO Bank 3 */
interrupts = <25 0x1>; /* Index: 25, rising edge */
spi-cpha; /* SPI mode: CPHA=1 */
spi-cpol; /* SPI mode: CPOL=1 */
spi-max-frequency = <8000000>; /* freq: 8 MHz */
local-mac-address = [ A0 B0 C0 D0 E0 F0 ];
};
};

View File

@ -21,6 +21,15 @@ Optional Properties:
can support. For example, a particular board has some signal
quality issue or the host processor can't support higher
baud rates.
- current-speed : The current baud rate the device operates at. This should
only be present in case a driver has no chance to know
the baud rate of the slave device.
Examples:
* device supports auto-baud
* the rate is setup by a bootloader and there is no
way to reset the device
* device baud rate is configured by its firmware but
there is no way to request the actual settings
Example:

View File

@ -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

View File

@ -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/

View File

@ -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;
}

View File

@ -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 */

View File

@ -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");

View File

@ -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

View File

@ -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;
}

View File

@ -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
@ -192,6 +191,30 @@ qcaspi_read_legacy(struct qcaspi *qca, u8 *dst, u32 len)
return 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)
{
@ -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");
@ -746,7 +769,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 +828,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 +917,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 +999,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");

View File

@ -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 */

View File

@ -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);