From patchwork Sun Sep 21 18:45:51 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 4944421 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork2.web.kernel.org (Postfix) with ESMTP id A6AB0BEEA5 for ; Sun, 21 Sep 2014 18:50:09 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id E9B4A20222 for ; Sun, 21 Sep 2014 18:50:06 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id E39CB20221 for ; Sun, 21 Sep 2014 18:50:03 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1XVmAf-0002bt-43; Sun, 21 Sep 2014 18:47:41 +0000 Received: from mout.gmx.net ([212.227.17.21]) by bombadil.infradead.org with esmtps (Exim 4.80.1 #2 (Red Hat Linux)) id 1XVmAV-0002As-68 for linux-arm-kernel@lists.infradead.org; Sun, 21 Sep 2014 18:47:34 +0000 Received: from zwerg.lan ([80.136.218.135]) by mail.gmx.com (mrgmx102) with ESMTPSA (Nemesis) id 0MC7em-1Xebqc0mDw-008t0q; Sun, 21 Sep 2014 20:46:58 +0200 From: Oleksij Rempel To: linux-arm-kernel@lists.infradead.org, tglx@linutronix.de, mturquette@linaro.org, jason@lakedaemon.net, gregkh@linuxfoundation.org Subject: [PATCH v2 8/8] tty/serial: add asm9260-serial driver Date: Sun, 21 Sep 2014 20:45:51 +0200 Message-Id: <1411325151-15107-7-git-send-email-linux@rempel-privat.de> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1411325151-15107-1-git-send-email-linux@rempel-privat.de> References: <1411324904-14881-1-git-send-email-linux@rempel-privat.de> <1411325151-15107-1-git-send-email-linux@rempel-privat.de> MIME-Version: 1.0 X-Provags-ID: V03:K0:Z9E7EYps5QXl98O2rC9qEUpB0XIToeiNOkJCIWdduyqHvtMAvRz MCKUP+pVWvoidx7CW0mUI4VmQsTCPWPTbR88oGeb6PoWXu4GZXDs+OUlDW1ZM5xZG55V5o3 KrYezgPe96mTbQu7x83EyRkYyetyzmUWVt0my9c9aFWQYvVV859/08qFpPQDgoPsPVo2jn+ kNv171DAAhvv2vgtBLefg== X-UI-Out-Filterresults: notjunk:1; X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140921_114731_775771_34B4473E X-CRM114-Status: GOOD ( 23.57 ) X-Spam-Score: -0.3 (/) Cc: Oleksij Rempel X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Spam-Status: No, score=-2.7 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_NONE, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Signed-off-by: Oleksij Rempel --- drivers/tty/serial/Kconfig | 17 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/asm9260_serial.c | 1485 +++++++++++++++++++++++++++++++++++ include/uapi/linux/serial_core.h | 2 + 4 files changed, 1505 insertions(+) create mode 100644 drivers/tty/serial/asm9260_serial.c diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 26cec64..9d80268 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1552,6 +1552,23 @@ config SERIAL_MEN_Z135 This driver can also be build as a module. If so, the module will be called men_z135_uart.ko +config SERIAL_ASM9260 + tristate "ASM9260 controller serial support" + depends on MACH_ASM9260 + select SERIAL_CORE + help + If you have an asm9260 based Base IO card + and wish to use the serial ports on this card, say Y. + Otherwise, say N. + +config SERIAL_ASM9260_CONSOLE + bool "ASM9260 console support" + depends on SERIAL_ASM9260 + select SERIAL_CORE_CONSOLE + help + If you want to support serial console on borad asm9260 + say Y. Otherwise, say N. + endmenu config SERIAL_MCTRL_GPIO diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 0080cc3..80940d4 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -95,3 +95,4 @@ obj-$(CONFIG_SERIAL_MEN_Z135) += men_z135_uart.o # GPIOLIB helpers for modem control lines obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o +obj-$(CONFIG_SERIAL_ASM9260) += asm9260_serial.o diff --git a/drivers/tty/serial/asm9260_serial.c b/drivers/tty/serial/asm9260_serial.c new file mode 100644 index 0000000..dc95478 --- /dev/null +++ b/drivers/tty/serial/asm9260_serial.c @@ -0,0 +1,1485 @@ +/* + * asm9260_serial.c Alphascale ASM9260 UART driver + * + * Mostly rewritten with irq_thread, clk and DT suppor: + * Copyright (C) 2014 Oleksij Rempel + * + * 2014 Cleaned up by Du Huanpeng + * + * Copyright (C) 2013, Alphascale Tech. Co., Ltd. + * Initial code has been inspired/copied from atmel_serial.c (kernel v2.4) and + * adopted for asm9260 (kernel v2.4) by Chen Dongdong. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +/* + * Note: This driver was tested and written for Alphascale ASM9260T. Since + * documentation for this SoC currently available only in Chinese, parts of it + * was recovered from similar devices. For example Alphascale ASAP1826 – which + * has identical offsets, but no support for RS485 and autoboud. And NXP + * Semiconductors LPC13xx (UM10375) – with identical registers and mostly + * identical functionality but different offsets. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define SERIAL_ASM9260_MAJOR 204 +#define MINOR_START 64 +#define ASM9260_DEVICENAME "ttyS" +#define DRIVER_NAME "asm9260_uart" +#define ASM9260_UART_FIFOSIZE 16 +#define ASM9260_BUS_RATE 100000000 +#define ASM9260_MAX_UART 10 + +#define UART_BAUD_DIVINT_MASK ((unsigned int)0x003FFFC0) +#define UART_BAUD_DIVFRAC_MASK ((unsigned int)0x0000003F) +#define UART_BAUD_DIV_MAX 0x3FFFFF + +/* + * this device provide 4 offsets for each register: + * 0x0 - plain read write mode + * 0x4 - set mode, OR logic. + * 0x8 - clr mode, XOR logic. + * 0xc - togle mode. + */ +#define SET_REG 0x4 +#define CLR_REG 0x8 + +/* RX ctrl register */ +#define HW_CTRL0 0x0000 +/* RW. Set to zero for normal operation. */ +#define BM_CTRL0_SFTRST BIT(31) +/* + * RW. 0 for normal operation; 1 gates all of the block level clocks off for + * miniminizing AC energy consumption. + */ +#define BM_CTRL0_CLKGATE BIT(30) +/* + * RW. Tell the UART to execute the RX DMA Command. The + * UART will clear this bit at the end of receive execution. + */ +#define BM_CTRL0_RXDMA_RUN BIT(28) +/* RW. 0 use FIFO for status register; 1 use DMA */ +#define BM_CTRL0_RXTO_SOURCE_STATUS BIT(25) +/* + * RW. RX TIMEOUT Enable. Valid for FIFO and DMA. + * Warning: If this bit is set to 0, the RX timeout will not affect receive DMA + * operation. If this bit is set to 1, a receive timeout will cause the receive + * DMA logic to terminate by filling the remaining DMA bytes with garbage data. + */ +#define BM_CTRL0_RXTO_ENABLE BIT(24) +/* + * RW. Receive Timeout Counter Value: number of 8-bit-time to wait before + * asserting timeout on the RX input. If the RXFIFO is not empty and the RX + * input is idle, then the watchdog counter will decrement each bit-time. Note + * 7-bit-time is added to the programmed value, so a value of zero will set + * the counter to 7-bit-time, a value of 0x1 gives 15-bit-time and so on. Also + * note that the counter is reloaded at the end of each frame, so if the frame + * is 10 bits long and the timeout counter value is zero, then timeout will + * occur (when FIFO is not empty) even if the RX input is not idle. The default + * value is 0x3 (31 bit-time). + */ +#define BM_CTRL0_RXTO_MASK (0xff<<16) +/* TIMEOUT = (100*7+1)*(1/BAUD) */ +#define BM_CTRL0_DEFAULT_RXTIMEOUT (20<<16) +/* RW. Number of bytes to receive. This must be a multiple of 4 */ +#define BM_CTRL0_RXDMA_COUNT_MASK (0xffff<<0) + +/* TX ctrl register */ +#define HW_CTRL1 0x0010 +/* + * RW. Tell the UART to execute the TX DMA Command. The + * UART will clear this bit at the end of transmit execution. + */ +#define BM_CTRL1_TXDMA_RUN BIT(28) +/* RW. Number of bytes to transmit. */ +#define BM_CTRL1_TXDMA_COUNT_MASK (0xffff << 0) + +#define HW_CTRL2 0x0020 +/* + * RW. Receive dma will terminate on error. (Cmd_end signal may not be asserted + * when this occurs.) + */ +#define BM_CTRL2_DMAONERROR BIT(26) +/* + * RW. Transmit DMA Enable. Data Register can be loaded with up to 4 bytes per + * write. TXFIFO must be enabled in TXDMA mode. + */ +#define BM_CTRL2_TXDMAE BIT(25) +/* + * RW. Receive DMA Enable. Data Register can be contain up to 4 bytes per read. + * RXFIFO must be enabled in RXDMA mode. + */ +#define BM_CTRL2_RXDMAE BIT(24) +/* + * RW. Receive Interrupt FIFO Level Select. + * The trigger points for the receive interrupt are as follows: + * ONE_EIGHTHS = 0x0 Trigger on FIFO full to at least 2 of 16 entries. + * ONE_QUARTER = 0x1 Trigger on FIFO full to at least 4 of 16 entries. + * ONE_HALF = 0x2 Trigger on FIFO full to at least 8 of 16 entries. + * THREE_QUARTERS = 0x3 Trigger on FIFO full to at least 12 of 16 entries. + * SEVEN_EIGHTHS = 0x4 Trigger on FIFO full to at least 14 of 16 entries. + */ +#define BM_CTRL2_RXIFLSEL (7<<20) +#define BM_CTRL2_DEFAULT_RXIFLSEL (3<<20) +/* RW. Same as RXIFLSEL */ +#define BM_CTRL2_TXIFLSEL (7<<16) +#define BM_CTRL2_DEFAULT_TXIFLSEL (2<<16) +/* RW. CTS Enable */ +#define BM_CTRL2_CTSE BIT(15) +/* RW. RTS Enable */ +#define BM_CTRL2_RTSE BIT(14) +/* + * RW. Manually trigger RTS. Works only if BM_CTRL2_RTSE = 0. + * When this bit is 1, the output is 0. + */ +#define BM_CTRL2_RTS BIT(11) +/* RW. Set DTR. When this bit is 1, the output is 0. */ +#define BM_CTRL2_DTR BIT(10) +/* RW. RX Enable */ +#define BM_CTRL2_RXE BIT(9) +/* RW. TX Enable */ +#define BM_CTRL2_TXE BIT(8) +/* RW. Loop Back Enable */ +#define BM_CTRL2_LBE BIT(7) +#define BM_CTRL2_PORT_ENABLE BIT(0) + +#define HW_LINECTRL 0x0030 +#define BM_LCTRL_BAUD_DIVINT (0xFFFF<<16) +#define BM_LCTRL_BAUD_DIVFRA (0x3F<<8) +/* + * RW. Stick Parity Select. When bits 1, 2, and 7 of this register are set, the + * parity bit is transmitted and checked as a 0. When bits 1 and 7 are set, + * and bit 2 is 0, the parity bit is transmitted and checked as a 1. When this + * bit is cleared stick parity is disabled. + */ +#define BM_LCTRL_SPS BIT(7) +/* RW. Word length */ +#define BM_LCTRL_WLEN (3<<5) +#define BM_LCTRL_CHRL_5 (0<<5) +#define BM_LCTRL_CHRL_6 (1<<5) +#define BM_LCTRL_CHRL_7 (2<<5) +#define BM_LCTRL_CHRL_8 (3<<5) +/* + * RW. Enable FIFOs. If this bit is set to 1, transmit and receive FIFO buffers + * are enabled (FIFO mode). When cleared to 0, the FIFOs are disabled (character + * mode); that is, the FIFOs become 1-byte-deep holding registers. + */ +#define BM_LCTRL_FEN BIT(4) +/* + * RW. Two Stop Bits Select. If this bit is set to 1, two stop bits are + * transmitted at the end of the frame. The receive logic does not check for + * two stop bits being received. + */ +#define BM_LCTRL_STP2 BIT(3) +#define BM_LCTRL_NBSTOP_1 (0<<3) +#define BM_LCTRL_NBSTOP_2 (1<<3) +/* RW. Even Parity Select. If disabled, then odd parity is performed. */ +#define BM_LCTRL_EPS BIT(2) +/* Parity Enable. */ +#define BM_LCTRL_PEN BIT(1) +#define BM_LCTRL_PAR_MARK ((3<<1) | (1<<7)) +#define BM_LCTRL_PAR_SPACE ((1<<1) | (1<<7)) +#define BM_LCTRL_PAR_ODD ((1<<1) | (0<<7)) +#define BM_LCTRL_PAR_EVEN ((3<<1) | (0<<7)) +#define BM_LCTRL_PAR_NONE (0<<1) +/* + * RW. Send Break. If this bit is set to 1, a low-level is continually output on + * the UARTTXD output, after completing transmission of the current character. + * For the proper execution of the break command, the software must set this bit + * for at least two complete frames. For normal use, this bit must be cleared + * to 0. + */ +#define BM_LCTRL_BREAK BIT(0) + +/* + * Interrupt register. + * contains the interrupt enables and the interrupt status bits + */ +#define HW_INTR 0x0040 +/* Tx FIFO EMPTY Raw Interrupt enable */ +#define BM_INTR_TFEIEN BIT(27) +/* Overrun Error Interrupt Enable. */ +#define BM_INTR_OEIEN BIT(26) +/* Break Error Interrupt Enable. */ +#define BM_INTR_BEIEN BIT(25) +/* Parity Error Interrupt Enable. */ +#define BM_INTR_PEIEN BIT(24) +/* Framing Error Interrupt Enable. */ +#define BM_INTR_FEIEN BIT(23) +/* + * RW. Receive Timeout Interrupt Enable. + * If not set and FIFO is enabled, then RX will be triggered only + * if FIFO is full. + */ +#define BM_INTR_RTIEN BIT(22) +/* Transmit Interrupt Enable. */ +#define BM_INTR_TXIEN BIT(21) +/* Receive Interrupt Enable. */ +#define BM_INTR_RXIEN BIT(20) +/* nUARTDSR Modem Interrupt Enable. */ +#define BM_INTR_DSRMIEN BIT(19) +/* nUARTDCD Modem Interrupt Enable. */ +#define BM_INTR_DCDMIEN BIT(18) +/* nUARTCTS Modem Interrupt Enable. */ +#define BM_INTR_CTSMIEN BIT(17) +/* nUARTRI Modem Interrupt Enable. */ +#define BM_INTR_RIMIEN BIT(16) +/* Auto-Boud Timeout */ +#define BM_INTR_ABTO BIT(13) +#define BM_INTR_ABEO BIT(12) +/* Tx FIFO EMPTY Raw Interrupt state */ +#define BM_INTR_TFEIS BIT(11) +/* Overrun Error */ +#define BM_INTR_OEIS BIT(10) +/* Break Error */ +#define BM_INTR_BEIS BIT(9) +/* Parity Error */ +#define BM_INTR_PEIS BIT(8) +/* Framing Error */ +#define BM_INTR_FEIS BIT(7) +/* Receive Timeout */ +#define BM_INTR_RTIS BIT(6) +/* Transmit done */ +#define BM_INTR_TXIS BIT(5) +/* Receive done */ +#define BM_INTR_RXIS BIT(4) +#define BM_INTR_DSRMIS BIT(3) +#define BM_INTR_DCDMIS BIT(2) +#define BM_INTR_CTSMIS BIT(1) +#define BM_INTR_RIMIS BIT(0) +#define BM_INTR_DEF_MASK (BM_INTR_RXIEN | BM_INTR_TXIEN | BM_INTR_RTIEN \ + | BM_INTR_FEIEN | BM_INTR_PEIEN | BM_INTR_BEIEN | BM_INTR_OEIEN) +#define BM_INTR_DEF_IS_MASK (BM_INTR_DEF_MASK >> 16) +#define BM_INTR_EN_MASK (0x3fff0000) +#define BM_INTR_IS_MASK (0x00003fff) + +/* + * RW. In DMA mode, up to 4 Received/Transmit characters can be accessed at a + * time. In PIO mode, only one character can be accessed at a time. The status + * register contains the receive data flags and valid bits. + */ +#define HW_DATA 0x0050 + +#define HW_STAT 0x0060 +/* RO. If 1, UARTAPP is present in this product. */ +#define BM_STAT_PRESENT BIT(31) +/* RO. If 1, HISPEED is present in this product. */ +#define BM_STAT_HISPEED BIT(30) +/* RO. UART Busy. */ +#define BM_STAT_BUSY BIT(29) +/* RO. Clear To Send. */ +#define BM_STAT_CTS BIT(28) +/* RO. Transmit FIFO/PIO Empty */ +#define BM_STAT_TXEMPTY BIT(27) +/* RO. Receive FIFO Full. */ +#define BM_STAT_RXFULL BIT(26) +/* RO. Transmit FIFO Full. */ +#define BM_STAT_TXFULL BIT(25) +/* RO. Receive FIFO Empty. */ +#define BM_STAT_RXEMPTY BIT(24) +/* + * RW. The invalid state of the last read of Receive Data. Each + * bit corresponds to one byte of the RX data. (1 = invalid.) + */ +#define BM_STAT_RXBYTE_INVALID_MASK (0xf<<20) +/* + * RO. Overrun Error. This bit is set to 1 if data is received and the FIFO is + * already full. This bit is cleared to 0 by any write to the Status Register. + * The FIFO contents remain valid since no further data is written when the + * FIFO is full; only the contents of the shift register are overwritten. The + * CPU must now read the data in order to empty the FIFO. + */ +#define BM_STAT_OVERRUNERR BIT(19) +/* + * RW. Break Error. For PIO mode, this is for the last character read from the + * data register. For DMA mode, it will be set to 1 if any received character + * for a particular RXDMA command had a Break Error. To clear this bit, write a + * zero to it. Note that clearing this bit does not affect the interrupt status, + * which must be cleared by writing the interrupt register. + */ +#define BM_STAT_BREAKERR BIT(18) +/* RW. Parity Error. Same as BREAKERR. */ +#define BM_STAT_PARITYERR BIT(17) +/* RW. Framing Erro. Same as BREAKERR. */ +#define BM_STAT_FRAMEERR BIT(16) +/* RO. Number of bytes received during a Receive DMA command. */ +#define BM_STAT_RXCOUNT_MASK (0xffff<<0) + +/* RO. The UART Debug Register contains the state of the DMA signals. */ +#define HW_DEBUG 0x0070 +/* DMA Command Run Status */ +#define BM_DEBUG_TXDMARUN BIT(5) +#define BM_DEBUG_RXDMARUN BIT(4) +/* DMA Command End Status */ +#define BM_DEBUG_TXCMDEND BIT(3) +#define BM_DEBUG_RXCMDEND BIT(2) +/* DMA Request Status */ +#define BM_DEBUG_TXDMARQ BIT(1) +#define BM_DEBUG_RXDMARQ BIT(0) + +#define HW_ILPR 0x0080 + +#define HW_RS485CTRL 0x0090 +/* + * RW. This bit reverses the polarity of the direction control signal on the RTS + * (or DTR) pin. + * If 0, The direction control pin will be driven to logic ‘0’ when the + * transmitter has data to be sent. It will be driven to logic ‘1’ after the + * last bit of data has been transmitted. + */ +#define BM_RS485CTRL_ONIV BIT(5) +/* RW. Enable Auto Direction Control. */ +#define BM_RS485CTRL_DIR_CTRL BIT(4) +/* + * RW. If 0 and DIR_CTRL = 1, pin RTS is used for direction control. + * If 1 and DIR_CTRL = 1, pin DTR is used for direction control. + */ +#define BM_RS485CTRL_PINSEL BIT(3) +/* RW. Enable Auto Address Detect (AAD). */ +#define BM_RS485CTRL_AADEN BIT(2) +/* RW. Disable receiver. */ +#define BM_RS485CTRL_RXDIS BIT(1) +/* RW. Enable RS-485/EIA-485 Normal Multidrop Mode (NMM) */ +#define BM_RS485CTRL_RS485EN BIT(0) + +#define HW_RS485ADRMATCH 0x00a0 +/* Contains the address match value. */ +#define BM_RS485ADRMATCH_MASK (0xff<<0) + +#define HW_RS485DLY 0x00b0 +/* + * RW. Contains the direction control (RTS or DTR) delay value. This delay time + * is in periods of the baud clock. + */ +#define BM_RS485DLY_MASK (0xff<<0) + +#define HW_AUTOBAUD 0x00c0 +/* WO. Auto-baud time-out interrupt clear bit. */ +#define BM_AUTOBAUD_ABTOIntClr BIT(9) +/* WO. End of auto-baud interrupt clear bit. */ +#define BM_AUTOBAUD_ABEOIntClr BIT(8) +/* Restart in case of timeout (counter restarts at next UART Rx falling edge) */ +#define BM_AUTOBAUD_AUTORESTART BIT(2) +/* Auto-baud mode select bit. 0 - Mode 0, 1 - Mode 1. */ +#define BM_AUTOBAUD_MODE BIT(1) +/* + * Auto-baud start (auto-baud is running). Auto-baud run bit. This bit is + * automatically cleared after auto-baud completion. + */ +#define BM_AUTOBAUD_START BIT(0) + +#define HW_CTRL3 0x00d0 +#define BM_CTRL3_OUTCLK_DIV_MASK (0xffff<<16) +/* + * RW. Provide clk over OUTCLK pin. In case of asm9260 it can be configured on + * pins 137 and 144. + */ +#define BM_CTRL3_MASTERMODE BIT(6) +/* RW. Baud Rate Mode: 1 - Enable sync mode. 0 - async mode. */ +#define BM_CTRL3_SYNCMODE BIT(4) +/* RW. 1 - MSB bit send frist; 0 - LSB bit frist. */ +#define BM_CTRL3_MSBF BIT(2) +/* RW. 1 - sample rate = 8 x Baudrate; 0 - sample rate = 16 x Baudrate. */ +#define BM_CTRL3_BAUD8 BIT(1) +/* RW. 1 - Set word lenght to 9bit. 0 - use BM_LCTRL_WLEN */ +#define BM_CTRL3_9BIT BIT(0) + +#define HW_ISO7816_CTRL 0x00e0 +/* RW. Enable High Speed mode. */ +#define BM_ISO7816CTRL_HS BIT(12) +/* Disable Successive Receive NACK */ +#define BM_ISO7816CTRL_DS_NACK BIT(8) +#define BM_ISO7816CTRL_MAX_ITER_MASK (0xff<<4) +/* Receive NACK Inhibit */ +#define BM_ISO7816CTRL_INACK BIT(3) +#define BM_ISO7816CTRL_NEG_DATA BIT(2) +/* RW. 1 - ISO7816 mode; 0 - USART mode */ +#define BM_ISO7816CTRL_ENABLE BIT(0) + +#define HW_ISO7816_ERRCNT 0x00f0 +/* Parity error counter. Will be cleared after reading */ +#define BM_ISO7816_NB_ERRORS_MASK (0xff<<0) + +#define HW_ISO7816_STATUS 0x0100 +/* Max number of Repetitions Reached */ +#define BM_ISO7816_STAT_ITERATION BIT(0) + +/* + * We wrap our port structure around the generic uart_port. + */ +struct asm9260_uart_port { + struct clk *clk; /* uart clock */ + struct clk *clk_ahb; + int clk_on; + int init_ok; + struct uart_port uart; /* uart */ + struct serial_rs485 rs485; /* rs485 settings */ +}; + +static struct asm9260_uart_port *asm9260_ports; +static int asm9260_ports_num; + +static void asm9260_start_rx(struct uart_port *uport); +static void asm9260_tx_chars(struct uart_port *uport); +static int asm9260_get_of_clks(struct asm9260_uart_port *port, + struct device_node *np); +static void asm9260_enable_clks(struct asm9260_uart_port *port); +static void asm9260_uart_of_enumerate(void); + +static inline struct asm9260_uart_port * +to_asm9260_uart_port(struct uart_port *uart) +{ + return container_of(uart, struct asm9260_uart_port, uart); +} + +static inline void asm9260_intr_mask(struct uart_port *uport) +{ + iowrite32(BM_INTR_DEF_MASK, + uport->membase + HW_INTR + CLR_REG); +} + +static inline void asm9260_intr_unmask(struct uart_port *uport) +{ + iowrite32(BM_INTR_DEF_MASK, + uport->membase + HW_INTR + SET_REG); +} + +/* + * Return TIOCSER_TEMT when transmitter FIFO and Shift register is empty. + */ +static u_int asm9260_tx_empty(struct uart_port *uport) +{ + return (ioread32(uport->membase + HW_STAT) + & BM_STAT_TXEMPTY) ? TIOCSER_TEMT : 0; +} + +static void asm9260_set_mctrl(struct uart_port *uport, u_int mctrl) +{ +} + +static u_int asm9260_get_mctrl(struct uart_port *uport) +{ + return 0; +} + +/* + * Stop transmitting. + */ +static void asm9260_stop_tx(struct uart_port *uport) +{ + struct asm9260_uart_port *asm9260_port = to_asm9260_uart_port(uport); + + /* seems like it was take over from atmel_serial.c + * do we need it too? */ + if ((asm9260_port->rs485.flags & SER_RS485_ENABLED) && + !(asm9260_port->rs485.flags & SER_RS485_RX_DURING_TX)) + asm9260_start_rx(uport); +} + +static void asm9260_start_tx(struct uart_port *uport) +{ + asm9260_tx_chars(uport); +} + +static void asm9260_start_rx(struct uart_port *uport) +{ + /* enable receive */ + iowrite32(BM_CTRL2_RXE, + uport->membase + HW_CTRL2 + SET_REG); +} + +static void asm9260_stop_rx(struct uart_port *uport) +{ + /* disable receive */ + iowrite32(BM_CTRL2_RXE, + uport->membase + HW_CTRL2 + CLR_REG); +} + +static void asm9260_enable_ms(struct uart_port *uport) +{ +} + +/* + * Control the transmission of a break signal + */ +static void asm9260_break_ctl(struct uart_port *uport, int break_state) +{ + if (break_state != 0) + iowrite32(BM_LCTRL_BREAK, + uport->membase + HW_LINECTRL + SET_REG); + else + iowrite32(BM_LCTRL_BREAK, + uport->membase + HW_LINECTRL + CLR_REG); +} + +static void asm9260_rx_chars(struct uart_port *uport, unsigned int intr) +{ + unsigned int status, ch; + + status = ioread32(uport->membase + HW_STAT); + while (!(status & BM_STAT_RXEMPTY)) { + unsigned int flg; + + ch = ioread32(uport->membase + HW_DATA); + + uport->icount.rx++; + flg = TTY_NORMAL; + + if (unlikely(intr & (BM_INTR_PEIS | BM_INTR_FEIS + | BM_INTR_OEIS | BM_INTR_BEIS))) { + + /* clear error */ + iowrite32(0, uport->membase + HW_STAT); + + if (intr & BM_INTR_BEIS) { + uport->icount.brk++; + if (uart_handle_break(uport)) + continue; + } else if (intr & BM_INTR_PEIS) + uport->icount.parity++; + else if (intr & BM_INTR_FEIS) + uport->icount.frame++; + + if (intr & BM_INTR_OEIS) + uport->icount.overrun++; + + intr &= uport->read_status_mask; + + if (intr & BM_INTR_BEIS) + flg = TTY_BREAK; + else if (intr & BM_INTR_PEIS) + flg = TTY_PARITY; + else if (intr & BM_INTR_FEIS) + flg = TTY_FRAME; + + } + + if (uart_handle_sysrq_char(uport, ch)) + continue; + + uart_insert_char(uport, intr, BM_INTR_OEIS, ch, flg); + status = ioread32(uport->membase + HW_STAT); + } + + tty_flip_buffer_push(&uport->state->port); +} + +static void asm9260_tx_chars(struct uart_port *uport) +{ + struct circ_buf *xmit = &uport->state->xmit; + + if (uport->x_char && !(ioread32(uport->membase + HW_STAT) + & BM_STAT_TXFULL)) { + iowrite32(uport->x_char, uport->membase + HW_DATA); + uport->icount.tx++; + uport->x_char = 0; + } + if (uart_circ_empty(xmit) || uart_tx_stopped(uport)) + return; + + while (!uart_circ_empty(xmit)) { + if (ioread32(uport->membase + HW_STAT) + & BM_STAT_TXFULL) + break; + + iowrite32(xmit->buf[xmit->tail], + uport->membase + HW_DATA); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + uport->icount.tx++; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(uport); +} + +static void +asm9260_handle_receive(struct uart_port *uport, unsigned int pending) +{ + /* Interrupt receive */ + if ((pending & BM_INTR_RXIS) || (pending & BM_INTR_RTIS)) + asm9260_rx_chars(uport, pending); + else if (pending & BM_INTR_BEIS) { + /* + * End of break detected. If it came along with a + * character, asm9260_rx_chars will handle it. + */ + iowrite32(0, uport->membase + HW_STAT); + } +} + +static void +asm9260_handle_transmit(struct uart_port *uport, unsigned int pending) +{ + if (pending & BM_INTR_TXIS) + asm9260_tx_chars(uport); +} + +/* + * Interrupt handler + */ +static irqreturn_t asm9260_interrupt(int irq, void *dev_id) +{ + struct uart_port *uport = dev_id; + unsigned int status; + + status = ioread32(uport->membase + HW_INTR); + status &= BM_INTR_DEF_IS_MASK; + + asm9260_handle_receive(uport, status); + asm9260_handle_transmit(uport, status); + + iowrite32(status, + uport->membase + HW_INTR + CLR_REG); + + asm9260_intr_unmask(uport); + return IRQ_HANDLED; +} + +static irqreturn_t asm9260_fast_int(int irq, void *dev_id) +{ + struct uart_port *uport = dev_id; + unsigned int status; + + status = ioread32(uport->membase + HW_INTR); + if (!(status & BM_INTR_DEF_IS_MASK)) + return IRQ_NONE; + + asm9260_intr_mask(uport); + return IRQ_WAKE_THREAD; +} + +/* + * Perform initialization and enable port for reception + */ +static int asm9260_startup(struct uart_port *uport) +{ + int retval; + + /* + * Ensure that no interrupts are enabled otherwise when + * request_irq() is called we could get stuck trying to + * handle an unexpected interrupt + */ + iowrite32(0, uport->membase + HW_INTR); + + retval = devm_request_threaded_irq(uport->dev, uport->irq, + asm9260_fast_int, asm9260_interrupt, IRQF_SHARED, + dev_name(uport->dev), uport); + if (retval) { + dev_err(uport->dev, "Can't get irq\n"); + return retval; + } + + /* enable rx timeout */ + iowrite32(BM_CTRL0_RXTO_MASK | BM_CTRL0_RXTO_SOURCE_STATUS, + uport->membase + HW_CTRL0 + CLR_REG); + iowrite32(BM_CTRL0_DEFAULT_RXTIMEOUT | BM_CTRL0_RXTO_ENABLE, + uport->membase + HW_CTRL0 + SET_REG); + + + /* + * Finally, enable the serial port + * enable tx & rx + */ + iowrite32(BM_CTRL2_RXIFLSEL | BM_CTRL2_TXIFLSEL, + uport->membase + HW_CTRL2 + CLR_REG); + iowrite32(BM_CTRL2_PORT_ENABLE | BM_CTRL2_TXE | BM_CTRL2_RXE | + BM_CTRL2_DEFAULT_TXIFLSEL | + BM_CTRL2_DEFAULT_RXIFLSEL, + uport->membase + HW_CTRL2); + + asm9260_intr_unmask(uport); + return 0; +} + +/* + * Disable the port + */ +static void asm9260_shutdown(struct uart_port *uport) +{ + int timeout = 10000; + + /*wait for controller finish tx*/ + while (!(ioread32(uport->membase + HW_STAT) + & BM_STAT_TXEMPTY)) { + if (--timeout < 0) + break; + } + + /* + * Ensure everything is stopped. + */ + asm9260_stop_tx(uport); + asm9260_stop_rx(uport); +} + +/* + * Flush any TX data submitted for DMA. Called when the TX circular + * buffer is reset. + */ +static void asm9260_flush_buffer(struct uart_port *uport) +{ +} + +/* + * Power / Clock management. + */ +static void asm9260_serial_pm(struct uart_port *uport, unsigned int state, + unsigned int oldstate) +{ +} + +static void asm9260_set_rs485(struct uart_port *uport) +{ + struct asm9260_uart_port *port = to_asm9260_uart_port(uport); + unsigned int rs485_ctrl; + /* set RS485 */ + rs485_ctrl = ioread32(uport->membase + HW_RS485CTRL); + + /* Resetting serial mode to RS232 (0x0) */ + rs485_ctrl &= ~BM_RS485CTRL_RS485EN; + + if (port->rs485.flags & SER_RS485_ENABLED) { + dev_dbg(uport->dev, "Setting UART to RS485\n"); + if ((port->rs485.delay_rts_after_send) > 0) { + /* + * delay is (rs485conf->delay_rts_after_send * + * Bit Period * 1/16) + */ + iowrite32(port->rs485.delay_rts_after_send, + uport->membase + HW_RS485DLY); + } + + if ((port->rs485.flags & SER_RS485_RTS_ON_SEND) && + !(port->rs485.flags & SER_RS485_RTS_AFTER_SEND)) { + /* + * Set logical level for RTS pin equal to 1 when + * sending, and set logical level for RTS pin equal + * to 0 after sending + */ + rs485_ctrl |= BM_RS485CTRL_ONIV; + } else if (!(port->rs485.flags & SER_RS485_RTS_ON_SEND) && + (port->rs485.flags & SER_RS485_RTS_AFTER_SEND)) { + /* + * Set logical level for RTS pin equal to 0 when + * sending, and set logical level for RTS pin equal + * to 1 after sending + */ + rs485_ctrl &= ~BM_RS485CTRL_ONIV; + } else + dev_info(uport->dev, + "Please view RS485CTRL register in datasheet for more details.\n"); + + /* + * Enable RS485 and RTS is used to control direction + * automatically, + */ + rs485_ctrl |= BM_RS485CTRL_RS485EN | BM_RS485CTRL_DIR_CTRL; + rs485_ctrl &= ~BM_RS485CTRL_PINSEL; + + if (port->rs485.flags & SER_RS485_RX_DURING_TX) + dev_dbg(uport->dev, "hardware should support SER_RS485_RX_DURING_TX.\n"); + } else + dev_dbg(uport->dev, "Setting UART to RS232\n"); + + iowrite32(rs485_ctrl, uport->membase + HW_RS485CTRL); +} +/* + * Change the port parameters + */ +static void asm9260_set_termios(struct uart_port *uport, + struct ktermios *termios, struct ktermios *old) +{ + unsigned int mode, baud; + unsigned int bauddivint, bauddivfrac; + + + asm9260_intr_mask(uport); + + /* + * We don't support modem control lines. + */ + termios->c_cflag &= ~(HUPCL | CMSPAR); + termios->c_cflag |= CLOCAL; + + /* Get current mode register */ + mode = ioread32(uport->membase + HW_LINECTRL); + mode &= ~(BM_LCTRL_PEN | BM_LCTRL_EPS + | BM_LCTRL_STP2 | BM_LCTRL_FEN + | BM_LCTRL_WLEN | BM_LCTRL_SPS + | BM_LCTRL_BAUD_DIVFRA | BM_LCTRL_BAUD_DIVINT); + + baud = uart_get_baud_rate(uport, termios, old, + uport->uartclk * 4 / UART_BAUD_DIV_MAX, + uport->uartclk / 16); + bauddivint = + (((uport->uartclk << 2) / baud) & UART_BAUD_DIVINT_MASK) << 10; + bauddivfrac = + (((uport->uartclk << 2) / baud) & UART_BAUD_DIVFRAC_MASK) << 8; + /* byte size */ + switch (termios->c_cflag & CSIZE) { + case CS5: + mode |= BM_LCTRL_CHRL_5; + break; + case CS6: + mode |= BM_LCTRL_CHRL_6; + break; + case CS7: + mode |= BM_LCTRL_CHRL_7; + break; + default: + mode |= BM_LCTRL_CHRL_8; + break; + } + + /* enable fifo */ + mode |= BM_LCTRL_FEN; + + /* stop bits */ + if (termios->c_cflag & CSTOPB) + mode |= BM_LCTRL_NBSTOP_2; + else + mode |= BM_LCTRL_NBSTOP_1; + + /* parity */ + if (termios->c_cflag & PARENB) { + /* Mark or Space parity */ + if (termios->c_cflag & CMSPAR) { + if (termios->c_cflag & PARODD) + mode |= BM_LCTRL_PAR_MARK; + else + mode |= BM_LCTRL_PAR_SPACE; + } else if (termios->c_cflag & PARODD) + mode |= BM_LCTRL_PAR_ODD; + else + mode |= BM_LCTRL_PAR_EVEN; + } else + mode |= BM_LCTRL_PAR_NONE; + + spin_lock(&uport->lock); + + uport->read_status_mask = BM_INTR_OEIS; + if (termios->c_iflag & INPCK) + uport->read_status_mask |= (BM_INTR_FEIS | BM_INTR_PEIS); + if (termios->c_iflag & (IGNBRK | BRKINT | PARMRK)) + uport->read_status_mask |= BM_INTR_BEIS; + + /* + * Characters to ignore + */ + uport->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + uport->ignore_status_mask |= + (BM_INTR_FEIS | BM_INTR_PEIS); + if (termios->c_iflag & IGNBRK) { + uport->ignore_status_mask |= BM_INTR_BEIS; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + uport->ignore_status_mask |= BM_INTR_OEIS; + } + + /* update the per-port timeout */ + uart_update_timeout(uport, termios->c_cflag, baud); + + /* drain transmitter */ + while (!(ioread32(uport->membase + HW_STAT) + & BM_STAT_TXEMPTY)) + cpu_relax(); + + while (!(ioread32(uport->membase + HW_STAT) + & BM_STAT_RXEMPTY)) + ioread32(uport->membase + HW_DATA); + + asm9260_set_rs485(uport); + + /* set hardware flow control */ + if (termios->c_cflag & CRTSCTS) + iowrite32(BM_CTRL2_CTSE | BM_CTRL2_RTSE, + uport->membase + HW_CTRL2 + SET_REG); + else + iowrite32(BM_CTRL2_CTSE | BM_CTRL2_RTSE, + uport->membase + HW_CTRL2 + CLR_REG); + + /* set the parity, stop bits, data size and baud rate*/ + iowrite32(mode | bauddivint | bauddivfrac, + uport->membase + HW_LINECTRL); + + /* CTS flow-control and modem-status interrupts */ + if (UART_ENABLE_MS(uport, termios->c_cflag)) + uport->ops->enable_ms(uport); + + spin_unlock(&uport->lock); + + dev_dbg(uport->dev, + "mode:0x%x, baud:%d, bauddivint:0x%x, bauddivfrac:0x%x, ctrl2:0x%x\n", + mode, baud, bauddivint, bauddivfrac, + ioread32(uport->membase + HW_CTRL2)); + + asm9260_intr_unmask(uport); +} + +/* + * Return string describing the specified port + */ +static const char *asm9260_type(struct uart_port *uport) +{ + return (uport->type == PORT_ASM9260) ? DRIVER_NAME : NULL; +} + +/* + * Release the memory region(s) being used by 'port'. + */ +static void asm9260_release_port(struct uart_port *uport) +{ +} + +/* + * Request the memory region(s) being used by 'port'. + */ +static int asm9260_request_port(struct uart_port *uport) +{ + return 0; +} + +/* + * Configure/autoconfigure the port. + */ +static void asm9260_config_port(struct uart_port *uport, int flags) +{ + if (flags & UART_CONFIG_TYPE) { + uport->type = PORT_ASM9260; + asm9260_request_port(uport); + } +} + +/* + * Verify the new serial_struct (for TIOCSSERIAL). + */ +static int asm9260_verify_port(struct uart_port *uport, + struct serial_struct *ser) +{ + int ret = 0; + + if (ser->type != PORT_UNKNOWN && ser->type != PORT_ASM9260) + ret = -EINVAL; + if (uport->irq != ser->irq) + ret = -EINVAL; + if (ser->io_type != SERIAL_IO_MEM) + ret = -EINVAL; + if (uport->uartclk / 16 != ser->baud_base) + ret = -EINVAL; + if ((void *)uport->mapbase != ser->iomem_base) + ret = -EINVAL; + if (uport->iobase != ser->port) + ret = -EINVAL; + if (ser->hub6 != 0) + ret = -EINVAL; + return ret; +} + +/* Enable or disable the rs485 support */ +void asm9260_config_rs485(struct uart_port *uport, + struct serial_rs485 *rs485conf) +{ + struct asm9260_uart_port *port = to_asm9260_uart_port(uport); + + asm9260_intr_mask(uport); + spin_lock(&uport->lock); + + /* Disable interrupts */ + + port->rs485 = *rs485conf; + + asm9260_set_rs485(uport); + + /* Enable tx interrupts */ + spin_unlock(&uport->lock); + asm9260_intr_unmask(uport); + +} + +static int asm9260_ioctl(struct uart_port *uport, + unsigned int cmd, unsigned long arg) +{ + struct serial_rs485 rs485conf; + + switch (cmd) { + case TIOCSRS485: + if (copy_from_user(&rs485conf, (struct serial_rs485 *) arg, + sizeof(rs485conf))) + return -EFAULT; + + asm9260_config_rs485(uport, &rs485conf); + break; + + case TIOCGRS485: + if (copy_to_user((struct serial_rs485 *) arg, + &(to_asm9260_uart_port(uport)->rs485), + sizeof(rs485conf))) + return -EFAULT; + break; + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static struct uart_ops asm9260_pops = { + .tx_empty = asm9260_tx_empty, + .set_mctrl = asm9260_set_mctrl, + .get_mctrl = asm9260_get_mctrl, + .stop_tx = asm9260_stop_tx, + .start_tx = asm9260_start_tx, + .stop_rx = asm9260_stop_rx, + .enable_ms = asm9260_enable_ms, + .break_ctl = asm9260_break_ctl, + .startup = asm9260_startup, + .shutdown = asm9260_shutdown, + .flush_buffer = asm9260_flush_buffer, + .set_termios = asm9260_set_termios, + .type = asm9260_type, + .release_port = asm9260_release_port, + .request_port = asm9260_request_port, + .config_port = asm9260_config_port, + .verify_port = asm9260_verify_port, + .pm = asm9260_serial_pm, + .ioctl = asm9260_ioctl, +}; + +#ifdef CONFIG_SERIAL_ASM9260_CONSOLE + +static struct asm9260_uart_port *get_asm9260_uart_port(int line); +static struct console asm9260_console; + +static void asm9260_console_putchar(struct uart_port *uport, int ch) +{ + while (ioread32(uport->membase + HW_STAT) + & BM_STAT_TXFULL) + cpu_relax(); + iowrite32(ch, uport->membase + HW_DATA); +} + +/* + * Interrupts are disabled on entering + */ +static void asm9260_console_write(struct console *co, const char *s, + u_int count) +{ + struct uart_port *uport; + struct asm9260_uart_port *port; + unsigned int status; + int locked = 1; + + port = get_asm9260_uart_port(co->index); + uport = &port->uart; + + asm9260_intr_mask(uport); + + if (oops_in_progress) + locked = spin_trylock(&uport->lock); + else + spin_lock(&uport->lock); + + + uart_console_write(uport, s, count, asm9260_console_putchar); + + /* + * Finally, wait for transmitter to become empty + * and restore IMR + */ + do { + status = ioread32(uport->membase + HW_STAT); + } while (!(status & BM_STAT_TXEMPTY)); + + if (locked) + spin_unlock(&uport->lock); + + asm9260_intr_unmask(uport); +} + +/* + * If the port was already initialised (eg, by a boot loader), + * try to determine the current setup. + */ +static void __init asm9260_console_get_options(struct uart_port *port, + int *baud, int *parity, int *bits) +{ + unsigned int mr, quot, linectrl, bauddivint, bauddivfrc; + + /* + * If the baud rate generator isn't running, the port wasn't + * initialized by the boot loader. + */ + linectrl = ioread32(port->membase + HW_LINECTRL); + bauddivint = (linectrl & BM_LCTRL_BAUD_DIVINT) >> 16; + bauddivfrc = (linectrl & BM_LCTRL_BAUD_DIVFRA) >> 8; + quot = (bauddivint << 6) | bauddivfrc; + + if (!quot) + return; + + mr = linectrl & BM_LCTRL_WLEN; + if (mr == BM_LCTRL_CHRL_8) + *bits = 8; + else + *bits = 7; + + mr = linectrl & + (BM_LCTRL_PEN | BM_LCTRL_EPS | BM_LCTRL_SPS); + if (mr == BM_LCTRL_PAR_EVEN) + *parity = 'e'; + else if (mr == BM_LCTRL_PAR_ODD) + *parity = 'o'; + + /* + * The serial core only rounds down when matching this to a + * supported baud rate. Make sure we don't end up slightly + * lower than one of those, as it would make us fall through + * to a much lower baud rate than we really want. + */ + *baud = (port->uartclk * 4) / quot; +} + +static int __init asm9260_console_setup(struct console *co, char *options) +{ + struct uart_port *uport; + struct asm9260_uart_port *port; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + asm9260_uart_of_enumerate(); + + port = get_asm9260_uart_port(co->index); + uport = &port->uart; + + asm9260_enable_clks(port); + + iowrite32(BM_CTRL2_TXE | BM_CTRL2_RXE | BM_CTRL2_PORT_ENABLE, + uport->membase + HW_CTRL2 + SET_REG); + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + else + asm9260_console_get_options(uport, &baud, &parity, &bits); + + return uart_set_options(uport, co, baud, parity, bits, flow); +} + +static struct uart_driver asm9260_uart; + +static struct console asm9260_console = { + .name = ASM9260_DEVICENAME, + .write = asm9260_console_write, + .device = uart_console_device, + .setup = asm9260_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &asm9260_uart, +}; + +#define ASM9260_CONSOLE_DEVICE (&asm9260_console) + +/* + * Early console initialization (before VM subsystem initialized). + */ +static int __init asm9260_console_init(void) +{ + register_console(&asm9260_console); + return 0; +} + +console_initcall(asm9260_console_init); +#else +#define ASM9260_CONSOLE_DEVICE NULL +#endif + +static struct uart_driver asm9260_uart = { + .owner = THIS_MODULE, + .driver_name = DRIVER_NAME, + .dev_name = ASM9260_DEVICENAME, + .nr = ASM9260_MAX_UART, + .cons = ASM9260_CONSOLE_DEVICE, +}; + +/* Match table for of_platform binding */ +static struct of_device_id asm9260_of_match[] = { + { .compatible = "alphascale,asm9260-uart", }, + {} +}; +MODULE_DEVICE_TABLE(of, asm9260_of_match); + +static void asm9260_enable_clks(struct asm9260_uart_port *port) +{ + struct uart_port *uport = &port->uart; + int err; + + if (port->clk_on) + return; + + err = clk_set_rate(port->clk, ASM9260_BUS_RATE); + if (err) + dev_err(uport->dev, "Failed to set rate!\n"); + + err = clk_prepare_enable(port->clk); + if (err) + dev_err(uport->dev, "Failed to enable clk!\n"); + + err = clk_prepare_enable(port->clk_ahb); + if (err) + dev_err(uport->dev, "Failed to enable ahb_clk!\n"); + + uport->uartclk = clk_get_rate(port->clk); + port->clk_on = 1; +} + + +/* get devicetree clocks, if some thing wrong, warn about it */ +static int asm9260_get_of_clks(struct asm9260_uart_port *port, + struct device_node *np) +{ + int clk_idx = 0; + + port->clk = of_clk_get(np, clk_idx); + if (IS_ERR(port->clk)) + goto out_err; + + /* configure AHB clock */ + clk_idx = 1; + port->clk_ahb = of_clk_get(np, clk_idx); + if (IS_ERR(port->clk_ahb)) + goto out_err; + + return 0; +out_err: + pr_err("%s: Failed to get clk (%i)\n", __func__, clk_idx); + return 1; +} + +static int asm9260_get_count_of_nodes(const struct of_device_id *matches) +{ + int count = 0; + struct device_node *np; + + for_each_matching_node(np, matches) + count++; + + return count; +} + +static struct asm9260_uart_port *get_asm9260_uart_port(int line) +{ + if (line >= asm9260_ports_num) { + pr_err("%s: Line number overflow. Check DeviceTree!!", + __func__); + return NULL; + } + + return &asm9260_ports[line]; +} + +static void asm9260_uart_of_enumerate(void) +{ + static int enum_done; + struct device_node *np; + + if (enum_done) + return; + + asm9260_ports_num = asm9260_get_count_of_nodes(asm9260_of_match); + asm9260_ports = kcalloc(asm9260_ports_num, + sizeof(struct asm9260_uart_port), GFP_KERNEL); + + for_each_matching_node(np, asm9260_of_match) { + struct uart_port *uport; + struct asm9260_uart_port *port; + int line; + + line = of_alias_get_id(np, "serial"); + if (line < 0) { + pr_err("Error! Devicetree has no \"serial\" aliases\n"); + continue; + } + + port = get_asm9260_uart_port(line); + if (!port) + continue; + + uport = &port->uart; + if (asm9260_get_of_clks(port, np)) + return; + + uport->iotype = UPIO_MEM; + uport->flags = UPF_BOOT_AUTOCONF; + uport->ops = &asm9260_pops; + uport->fifosize = ASM9260_UART_FIFOSIZE; + uport->line = line; + + /* Since of_map don't do actual request of memory region, + * it is save to use it for all, enabled and disabled uarts. */ + uport->membase = of_iomap(np, 0); + if (!uport->membase) { + pr_err("Unable to map registers\n"); + continue; + } + port->init_ok = 1; + } + + enum_done = 1; +} + +/* + * Configure the port from the platform device resource info. + */ +static void asm9260_init_port(struct asm9260_uart_port *asm9260_port, + struct platform_device *pdev) +{ + struct uart_port *uport = &asm9260_port->uart; + struct device_node *np = pdev->dev.of_node; + struct resource res; + + uport->dev = &pdev->dev; + + uport->irq = irq_of_parse_and_map(np, 0); + + of_address_to_resource(np, 0, &res); + if (!devm_request_mem_region(uport->dev, res.start, + resource_size(&res), dev_name(uport->dev))) + dev_err(uport->dev, "unable to request mem region\n"); + + uport->mapbase = res.start; + + asm9260_enable_clks(asm9260_port); +} + +static int asm9260_serial_probe(struct platform_device *pdev) +{ + struct asm9260_uart_port *port; + struct device_node *np = pdev->dev.of_node; + int ret, line; + + asm9260_uart_of_enumerate(); + + if (!np) { + dev_err(&pdev->dev, "Error! We support only DeviceTree!\n"); + return -EPERM; + } + + line = of_alias_get_id(np, "serial"); + if (line < 0) { + dev_err(&pdev->dev, + "Error! Devicetree has no \"serial\" aliases\n"); + return -EPERM; + } + + port = get_asm9260_uart_port(line); + + if (!port->init_ok) + dev_err(&pdev->dev, "Bad init!\n"); + + asm9260_init_port(port, pdev); + + ret = uart_add_one_port(&asm9260_uart, &port->uart); + if (ret) { + dev_err(&pdev->dev, "Filed to add uart port\n"); + goto err_add_port; + } + + platform_set_drvdata(pdev, port); + + return 0; + +err_add_port: + if (!uart_console(&port->uart)) { + clk_put(port->clk); + port->clk = NULL; + } + dev_err(&pdev->dev, "Filed to probe device\n"); + return ret; +} + +static int asm9260_serial_remove(struct platform_device *pdev) +{ + struct uart_port *port = platform_get_drvdata(pdev); + struct asm9260_uart_port *asm9260_port = to_asm9260_uart_port(port); + int ret = 0; + + uart_remove_one_port(&asm9260_uart, port); + uart_unregister_driver(&asm9260_uart); + + /* TODO: how should we handle clks here */ + clk_put(asm9260_port->clk); + + return ret; +} + +static struct platform_driver asm9260_serial_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(asm9260_of_match), + }, + .probe = asm9260_serial_probe, + .remove = asm9260_serial_remove, +}; + +static int __init asm9260_serial_init(void) +{ + int ret; + + ret = uart_register_driver(&asm9260_uart); + if (ret) + return ret; + + ret = platform_driver_register(&asm9260_serial_driver); + if (ret) + uart_unregister_driver(&asm9260_uart); + + return ret; +} + +static void __exit asm9260_serial_exit(void) +{ + platform_driver_unregister(&asm9260_serial_driver); + uart_unregister_driver(&asm9260_uart); +} + +module_init(asm9260_serial_init); +module_exit(asm9260_serial_exit); + +MODULE_DESCRIPTION("ASM9260 serial port driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 5820269..ed68009 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -244,4 +244,6 @@ /* SC16IS74xx */ #define PORT_SC16IS7XX 108 +/* Alpscale ASM9260 */ +#define PORT_ASM9260 109 #endif /* _UAPILINUX_SERIAL_CORE_H */