From patchwork Mon Sep 26 11:08:06 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Wahren X-Patchwork-Id: 675094 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming@ozlabs.org Delivered-To: patchwork-incoming@ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3sjLpw0rx7z9s5g for ; Mon, 26 Sep 2016 21:10:16 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S966010AbcIZLIb (ORCPT ); Mon, 26 Sep 2016 07:08:31 -0400 Received: from mout.kundenserver.de ([212.227.126.131]:59852 "EHLO mout.kundenserver.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S964989AbcIZLI3 (ORCPT ); Mon, 26 Sep 2016 07:08:29 -0400 Received: from duckbill-v2-sdk.fritz.box ([109.104.38.120]) by mrelayeu.kundenserver.de (mreue001) with ESMTPSA (Nemesis) id 0MAHcX-1bdFM73hRB-00BKxh; Mon, 26 Sep 2016 13:08:23 +0200 From: Stefan Wahren To: "David S. Miller" , Greg Kroah-Hartman , Jiri Slaby Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org, Stefan Wahren Subject: [PATCH RFC v2 net-next 8/8] net: qualcomm: add QCA7000 UART driver Date: Mon, 26 Sep 2016 13:08:06 +0200 Message-Id: <1474888086-514-9-git-send-email-stefan.wahren@i2se.com> X-Mailer: git-send-email 2.1.4 In-Reply-To: <1474888086-514-1-git-send-email-stefan.wahren@i2se.com> References: <1474888086-514-1-git-send-email-stefan.wahren@i2se.com> X-Provags-ID: V03:K0:HEwozO4uI9oiSAqiR8NMbakXgAx6z2AwBmMgwgxj6GesehFy56/ QhlPlsN91daPvA1VEtj2f+FVjhrU+pTylflpJ2wBt1k92KzB2HNgaGqqEnsovCWmAz6/KTH Yk94nxZEPeYBgxg2XvS8zDGac1k9bFWUWjQ7mv0XJ6l/9kSJeav+Kz4AqfkLBwwiD9e2NAP MAZ/yaDzZG3Lat+2dybBg== X-UI-Out-Filterresults: notjunk:1; V01:K0:hZvuNOPxqWE=:Vis41Nz1oIG1hBYBjjEIrm yzYf9snG+B5gMrxfYHPA/3kbFf+jNE0U6tmx5+MmGjGWKe8mrhn5UybzAsyQpdL62H/PfGcNf sRlFYb4OF5HiqVWSI/6pN1Tl4w8vo74Net/YMTevnOVwEQhVOJyIfRs/QL6sWEkdvThTY7YfF v6f/EHPDIoTRj6+jDQHC8VJEtVqUWDxyYOMp2jOjMjrSrLgp1bxBfPlBwrNp+dRnL+HBCf9sE UB3WIDj3BNbmWGUpeyC1mTq4ygpkPc39/FLo7Teivl9Fe1Ufb6UFCfZ7Uq+5ELF0+Angf/gHA gcNm/oyFfZ5+qGwcFevy+X2X4j37krSYLm2os5LwQH9NwVJazlKus2yB6dOeOK+zlYSHIj83L rsr0r2Mtb9XLeRa9UASFTkgwUWGiGU2X8JFr9JkpSx8vCwV7lNNBWJQbN/9/xMot8jBUmv6FD hJkHWZtkyvA9V/SwjJCBzNKq5ewlrkwnvQUzoSFBqVmI7QKNS380bM49wHnFAwn5c4UOgYAil OIqD9EBcZrfTdPUoBjtk3T2rDjWZTYZ/eNlM8uWB+HU5s3+8tZkRi6Z4AHViGbJQ7r9WrbFrJ wZfAHbVytmkQlelJjenJS2ey3IFT2qHXAHhPO+EOPWtbrJ1Im8R8BR3LvIt3iPngSVMOwKLj1 v0OSDbxQBe5z6Fmk7I2Jiyiis3yrhGEVRW9CepfEYCRDZecJWqpzJI2IoZLaKvt/Tz8Y= Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This patch adds the Ethernet over UART driver for the Qualcomm QCA7000 HomePlug GreenPHY. Signed-off-by: Stefan Wahren --- drivers/net/ethernet/qualcomm/Kconfig | 12 + drivers/net/ethernet/qualcomm/Makefile | 2 + drivers/net/ethernet/qualcomm/qca_common.h | 6 + drivers/net/ethernet/qualcomm/qca_uart.c | 447 +++++++++++++++++++++++++++++ include/uapi/linux/tty.h | 1 + 5 files changed, 468 insertions(+) create mode 100644 drivers/net/ethernet/qualcomm/qca_uart.c diff --git a/drivers/net/ethernet/qualcomm/Kconfig b/drivers/net/ethernet/qualcomm/Kconfig index 0d33728..0ede46e 100644 --- a/drivers/net/ethernet/qualcomm/Kconfig +++ b/drivers/net/ethernet/qualcomm/Kconfig @@ -30,6 +30,18 @@ config QCA7000_SPI 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 TTY + ---help--- + This UART protocol driver supports the Qualcomm Atheros QCA7000. + The driver implements the tty line discipline N_QCA7K and supports + only one netdevice. + + 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" select CRC32 diff --git a/drivers/net/ethernet/qualcomm/Makefile b/drivers/net/ethernet/qualcomm/Makefile index 00d8729..8847db7 100644 --- a/drivers/net/ethernet/qualcomm/Makefile +++ b/drivers/net/ethernet/qualcomm/Makefile @@ -5,5 +5,7 @@ obj-$(CONFIG_QCA7000) += qca_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/qca_common.h b/drivers/net/ethernet/qualcomm/qca_common.h index c93cfdf07..bc38689 100644 --- a/drivers/net/ethernet/qualcomm/qca_common.h +++ b/drivers/net/ethernet/qualcomm/qca_common.h @@ -123,6 +123,12 @@ static inline void qcafrm_fsm_init_spi(struct qcafrm_handle *handle) 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 * by following a simple state machine. * diff --git a/drivers/net/ethernet/qualcomm/qca_uart.c b/drivers/net/ethernet/qualcomm/qca_uart.c new file mode 100644 index 0000000..0a8dd6d --- /dev/null +++ b/drivers/net/ethernet/qualcomm/qca_uart.c @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2011, 2012, Qualcomm Atheros Communications Inc. + * Copyright (c) 2016, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qca_common.h" + +#define QCAUART_DRV_VERSION "0.0.6" +#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 tty_struct *tty; + + unsigned char xbuff[QCAFRM_ETHMAXMTU]; /* transmitter buffer */ + unsigned char *xhead; /* pointer to next XMIT byte */ + int xleft; /* bytes left in XMIT queue */ + + struct qcafrm_handle frm_handle; + + struct sk_buff *rx_skb; +}; + +static struct net_device *qcauart_dev; + +void +qca_tty_receive(struct tty_struct *tty, const unsigned char *cp, char *fp, + int count) +{ + struct qcauart *qca = tty->disc_data; + struct net_device_stats *n_stats = &qca->net_dev->stats; + int dropped = 0; + + if (!qca->rx_skb) { + qca->rx_skb = netdev_alloc_skb(qca->net_dev, qca->net_dev->mtu + + VLAN_ETH_HLEN); + if (!qca->rx_skb) { + n_stats->rx_errors++; + n_stats->rx_dropped++; + return; + } + } + + while (count--) { + s32 retcode; + + if (fp && *fp++) { + n_stats->rx_errors++; + cp++; + dropped++; + continue; + } + + retcode = qcafrm_fsm_decode(&qca->frm_handle, + qca->rx_skb->data, + skb_tailroom(qca->rx_skb), + *cp); + + cp++; + switch (retcode) { + case QCAFRM_GATHER: + case QCAFRM_NOHEAD: + break; + case QCAFRM_NOTAIL: + netdev_dbg(qca->net_dev, "recv: no RX tail\n"); + n_stats->rx_errors++; + n_stats->rx_dropped++; + break; + case QCAFRM_INVLEN: + netdev_dbg(qca->net_dev, "recv: invalid RX length\n"); + n_stats->rx_errors++; + n_stats->rx_dropped++; + break; + default: + qca->rx_skb->dev = qca->net_dev; + 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(qca->net_dev, + qca->net_dev->mtu + + VLAN_ETH_HLEN); + if (!qca->rx_skb) { + netdev_dbg(qca->net_dev, "recv: out of RX resources\n"); + n_stats->rx_errors++; + break; + } + } + } + + if (dropped) + dev_dbg_ratelimited(&qca->net_dev->dev, "recv: invalid %d bytes\n", + dropped); +} + +/* 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 (!qca->tty || !netif_running(qca->net_dev)) { + spin_unlock_bh(&qca->lock); + return; + } + + if (qca->xleft <= 0) { + /* Now serial buffer is almost free & we can start + * transmission of another packet + */ + n_stats->tx_packets++; + clear_bit(TTY_DO_WRITE_WAKEUP, &qca->tty->flags); + spin_unlock_bh(&qca->lock); + netif_wake_queue(qca->net_dev); + return; + } + + written = qca->tty->ops->write(qca->tty, qca->xhead, qca->xleft); + qca->xleft -= written; + qca->xhead += 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 tty_struct *tty) +{ + struct qcauart *qca = tty->disc_data; + + schedule_work(&qca->tx_work); +} + +int +qca_tty_open(struct tty_struct *tty) +{ + struct qcauart *qca; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + if (!tty->ops->write) + return -EOPNOTSUPP; + + if (tty->disc_data) + return -EEXIST; + + qca = netdev_priv(qcauart_dev); + qca->tty = tty; + tty->disc_data = qca; + tty->receive_room = 4096; + netif_carrier_on(qca->net_dev); + + return 0; +} + +void +qca_tty_close(struct tty_struct *tty) +{ + struct qcauart *qca = (void *)tty->disc_data; + + if (!qca) + return; + + netif_carrier_off(qca->net_dev); + qca->tty = NULL; + + /* Detach from the tty */ + tty->disc_data = NULL; +} + +static struct tty_ldisc_ops qca_ldisc = { + .owner = THIS_MODULE, + .magic = TTY_LDISC_MAGIC, + .name = "qca", + .open = qca_tty_open, + .close = qca_tty_close, + .ioctl = n_tty_ioctl_helper, + .receive_buf = qca_tty_receive, + .write_wakeup = qca_tty_wakeup, +}; + +int +qcauart_netdev_open(struct net_device *dev) +{ + struct qcauart *qca = netdev_priv(dev); + + qcafrm_fsm_init_uart(&qca->frm_handle); + netif_start_queue(qca->net_dev); + + return 0; +} + +int +qcauart_netdev_close(struct net_device *dev) +{ + struct qcauart *qca = netdev_priv(dev); + + spin_lock_bh(&qca->lock); + if (qca->tty) { + /* TTY discipline is running. */ + clear_bit(TTY_DO_WRITE_WAKEUP, &qca->tty->flags); + } + netif_stop_queue(dev); + qca->xleft = 0; + spin_unlock_bh(&qca->lock); + + return 0; +} + +netdev_tx_t +qcauart_netdev_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct qcauart *qca = netdev_priv(dev); + struct net_device_stats *n_stats = &dev->stats; + u8 *pos; + u8 pad_len = 0; + int written; + + spin_lock(&qca->lock); + + if (!netif_running(dev)) { + spin_unlock(&qca->lock); + netdev_warn(qca->net_dev, "xmit: iface is down\n"); + goto out; + } + if (!qca->tty) { + spin_unlock(&qca->lock); + goto out; + } + + pos = qca->xbuff; + + if (skb->len < QCAFRM_ETHMINLEN) + pad_len = QCAFRM_ETHMINLEN - 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); + + set_bit(TTY_DO_WRITE_WAKEUP, &qca->tty->flags); + written = qca->tty->ops->write(qca->tty, qca->xbuff, pos - qca->xbuff); + qca->xleft = (pos - qca->xbuff) - written; + qca->xhead = qca->xbuff + written; + n_stats->tx_bytes += written; + spin_unlock(&qca->lock); + + netif_trans_update(dev); +out: + kfree_skb(skb); + return NETDEV_TX_OK; +} + +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++; + + clear_bit(TTY_DO_WRITE_WAKEUP, &qca->tty->flags); +} + +static int +qcauart_netdev_init(struct net_device *dev) +{ + struct qcauart *qca = netdev_priv(dev); + + /* Finish setting up the device info. */ + dev->mtu = QCAFRM_ETHMAXMTU; + dev->type = ARPHRD_ETHER; + + qca->rx_skb = netdev_alloc_skb(qca->net_dev, + qca->net_dev->mtu + VLAN_ETH_HLEN); + if (!qca->rx_skb) + return -ENOMEM; + + 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_change_mtu = qcacmn_netdev_change_mtu, + .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) +{ + struct qcauart *qca; + + dev->netdev_ops = &qcauart_netdev_ops; + dev->watchdog_timeo = QCAUART_TX_TIMEOUT; + dev->priv_flags &= ~IFF_TX_SKB_SHARING; + dev->tx_queue_len = 100; + + qca = netdev_priv(dev); + memset(qca, 0, sizeof(struct qcauart)); +} + +static int __init qcauart_mod_init(void) +{ + struct qcauart *qca; + int ret; + + ret = tty_register_ldisc(N_QCA7K, &qca_ldisc); + if (ret) { + pr_err("qca_uart: Can't register line discipline (ret %d)\n", + ret); + return ret; + } + + qcauart_dev = alloc_etherdev(sizeof(struct qcauart)); + if (!qcauart_dev) + return -ENOMEM; + + qcauart_netdev_setup(qcauart_dev); + + qca = netdev_priv(qcauart_dev); + if (!qca) { + pr_err("qca_uart: Fail to retrieve private structure\n"); + free_netdev(qcauart_dev); + return -ENOMEM; + } + qca->net_dev = qcauart_dev; + spin_lock_init(&qca->lock); + INIT_WORK(&qca->tx_work, qcauart_transmit); + + eth_hw_addr_random(qca->net_dev); + pr_info("qca_uart: ver=%s, using random MAC address: %pM\n", + QCAUART_DRV_VERSION, qca->net_dev->dev_addr); + + netif_carrier_off(qca->net_dev); + + if (register_netdev(qcauart_dev)) { + pr_err("qca_uart: Unable to register net device %s\n", + qcauart_dev->name); + free_netdev(qcauart_dev); + return -EFAULT; + } + + return 0; +} + +static void __exit qcauart_mod_exit(void) +{ + struct qcauart *qca; + int ret; + + qca = netdev_priv(qcauart_dev); + spin_lock_bh(&qca->lock); + if (qca->tty) + tty_hangup(qca->tty); + spin_unlock_bh(&qca->lock); + + unregister_netdev(qcauart_dev); + + free_netdev(qcauart_dev); + qcauart_dev = NULL; + + ret = tty_unregister_ldisc(N_QCA7K); + if (ret) + pr_err("qca_uart: can't unregister line discipline (ret %d)\n", + ret); +} + +module_init(qcauart_mod_init); +module_exit(qcauart_mod_exit); + +MODULE_DESCRIPTION("Qualcomm Atheros UART Driver"); +MODULE_AUTHOR("Qualcomm Atheros Communications"); +MODULE_AUTHOR("Stefan Wahren "); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_VERSION(QCAUART_DRV_VERSION); +MODULE_ALIAS_LDISC(N_QCA7K); diff --git a/include/uapi/linux/tty.h b/include/uapi/linux/tty.h index 01c4410..53c2760 100644 --- a/include/uapi/linux/tty.h +++ b/include/uapi/linux/tty.h @@ -35,5 +35,6 @@ #define N_TRACESINK 23 /* Trace data routing for MIPI P1149.7 */ #define N_TRACEROUTER 24 /* Trace data routing for MIPI P1149.7 */ #define N_NCI 25 /* NFC NCI UART */ +#define N_QCA7K 26 /* Qualcomm QCA7000 Ethernet to UART */ #endif /* _UAPI_LINUX_TTY_H */