2017-11-07 00:11:51 +07:00
|
|
|
// SPDX-License-Identifier: GPL-2.0+
|
2014-04-25 07:56:06 +07:00
|
|
|
/*
|
|
|
|
* SC16IS7xx tty serial driver - Copyright (C) 2014 GridPoint
|
|
|
|
* Author: Jon Ringle <jringle@gridpoint.com>
|
|
|
|
*
|
|
|
|
* Based on max310x.c, by Alexander Shiyan <shc_work@mail.ru>
|
|
|
|
*/
|
|
|
|
|
2015-07-31 19:44:23 +07:00
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
#include <linux/bitops.h>
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/device.h>
|
2015-12-09 05:15:53 +07:00
|
|
|
#include <linux/gpio/driver.h>
|
2014-04-25 07:56:06 +07:00
|
|
|
#include <linux/i2c.h>
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_device.h>
|
|
|
|
#include <linux/regmap.h>
|
|
|
|
#include <linux/serial_core.h>
|
|
|
|
#include <linux/serial.h>
|
|
|
|
#include <linux/tty.h>
|
|
|
|
#include <linux/tty_flip.h>
|
2015-05-25 13:21:09 +07:00
|
|
|
#include <linux/spi/spi.h>
|
2014-04-26 02:53:10 +07:00
|
|
|
#include <linux/uaccess.h>
|
2017-02-02 00:07:51 +07:00
|
|
|
#include <uapi/linux/sched/types.h>
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
#define SC16IS7XX_NAME "sc16is7xx"
|
2015-07-31 19:44:23 +07:00
|
|
|
#define SC16IS7XX_MAX_DEVS 8
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
/* SC16IS7XX register definitions */
|
|
|
|
#define SC16IS7XX_RHR_REG (0x00) /* RX FIFO */
|
|
|
|
#define SC16IS7XX_THR_REG (0x00) /* TX FIFO */
|
|
|
|
#define SC16IS7XX_IER_REG (0x01) /* Interrupt enable */
|
|
|
|
#define SC16IS7XX_IIR_REG (0x02) /* Interrupt Identification */
|
|
|
|
#define SC16IS7XX_FCR_REG (0x02) /* FIFO control */
|
|
|
|
#define SC16IS7XX_LCR_REG (0x03) /* Line Control */
|
|
|
|
#define SC16IS7XX_MCR_REG (0x04) /* Modem Control */
|
|
|
|
#define SC16IS7XX_LSR_REG (0x05) /* Line Status */
|
|
|
|
#define SC16IS7XX_MSR_REG (0x06) /* Modem Status */
|
|
|
|
#define SC16IS7XX_SPR_REG (0x07) /* Scratch Pad */
|
|
|
|
#define SC16IS7XX_TXLVL_REG (0x08) /* TX FIFO level */
|
|
|
|
#define SC16IS7XX_RXLVL_REG (0x09) /* RX FIFO level */
|
|
|
|
#define SC16IS7XX_IODIR_REG (0x0a) /* I/O Direction
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_IOSTATE_REG (0x0b) /* I/O State
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_IOINTENA_REG (0x0c) /* I/O Interrupt Enable
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_IOCONTROL_REG (0x0e) /* I/O Control
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_EFCR_REG (0x0f) /* Extra Features Control */
|
|
|
|
|
|
|
|
/* TCR/TLR Register set: Only if ((MCR[2] == 1) && (EFR[4] == 1)) */
|
|
|
|
#define SC16IS7XX_TCR_REG (0x06) /* Transmit control */
|
|
|
|
#define SC16IS7XX_TLR_REG (0x07) /* Trigger level */
|
|
|
|
|
|
|
|
/* Special Register set: Only if ((LCR[7] == 1) && (LCR != 0xBF)) */
|
|
|
|
#define SC16IS7XX_DLL_REG (0x00) /* Divisor Latch Low */
|
|
|
|
#define SC16IS7XX_DLH_REG (0x01) /* Divisor Latch High */
|
|
|
|
|
|
|
|
/* Enhanced Register set: Only if (LCR == 0xBF) */
|
|
|
|
#define SC16IS7XX_EFR_REG (0x02) /* Enhanced Features */
|
|
|
|
#define SC16IS7XX_XON1_REG (0x04) /* Xon1 word */
|
|
|
|
#define SC16IS7XX_XON2_REG (0x05) /* Xon2 word */
|
|
|
|
#define SC16IS7XX_XOFF1_REG (0x06) /* Xoff1 word */
|
|
|
|
#define SC16IS7XX_XOFF2_REG (0x07) /* Xoff2 word */
|
|
|
|
|
|
|
|
/* IER register bits */
|
|
|
|
#define SC16IS7XX_IER_RDI_BIT (1 << 0) /* Enable RX data interrupt */
|
|
|
|
#define SC16IS7XX_IER_THRI_BIT (1 << 1) /* Enable TX holding register
|
|
|
|
* interrupt */
|
|
|
|
#define SC16IS7XX_IER_RLSI_BIT (1 << 2) /* Enable RX line status
|
|
|
|
* interrupt */
|
|
|
|
#define SC16IS7XX_IER_MSI_BIT (1 << 3) /* Enable Modem status
|
|
|
|
* interrupt */
|
|
|
|
|
|
|
|
/* IER register bits - write only if (EFR[4] == 1) */
|
|
|
|
#define SC16IS7XX_IER_SLEEP_BIT (1 << 4) /* Enable Sleep mode */
|
|
|
|
#define SC16IS7XX_IER_XOFFI_BIT (1 << 5) /* Enable Xoff interrupt */
|
|
|
|
#define SC16IS7XX_IER_RTSI_BIT (1 << 6) /* Enable nRTS interrupt */
|
|
|
|
#define SC16IS7XX_IER_CTSI_BIT (1 << 7) /* Enable nCTS interrupt */
|
|
|
|
|
|
|
|
/* FCR register bits */
|
|
|
|
#define SC16IS7XX_FCR_FIFO_BIT (1 << 0) /* Enable FIFO */
|
|
|
|
#define SC16IS7XX_FCR_RXRESET_BIT (1 << 1) /* Reset RX FIFO */
|
|
|
|
#define SC16IS7XX_FCR_TXRESET_BIT (1 << 2) /* Reset TX FIFO */
|
|
|
|
#define SC16IS7XX_FCR_RXLVLL_BIT (1 << 6) /* RX Trigger level LSB */
|
|
|
|
#define SC16IS7XX_FCR_RXLVLH_BIT (1 << 7) /* RX Trigger level MSB */
|
|
|
|
|
|
|
|
/* FCR register bits - write only if (EFR[4] == 1) */
|
|
|
|
#define SC16IS7XX_FCR_TXLVLL_BIT (1 << 4) /* TX Trigger level LSB */
|
|
|
|
#define SC16IS7XX_FCR_TXLVLH_BIT (1 << 5) /* TX Trigger level MSB */
|
|
|
|
|
|
|
|
/* IIR register bits */
|
|
|
|
#define SC16IS7XX_IIR_NO_INT_BIT (1 << 0) /* No interrupts pending */
|
|
|
|
#define SC16IS7XX_IIR_ID_MASK 0x3e /* Mask for the interrupt ID */
|
|
|
|
#define SC16IS7XX_IIR_THRI_SRC 0x02 /* TX holding register empty */
|
|
|
|
#define SC16IS7XX_IIR_RDI_SRC 0x04 /* RX data interrupt */
|
|
|
|
#define SC16IS7XX_IIR_RLSE_SRC 0x06 /* RX line status error */
|
|
|
|
#define SC16IS7XX_IIR_RTOI_SRC 0x0c /* RX time-out interrupt */
|
|
|
|
#define SC16IS7XX_IIR_MSI_SRC 0x00 /* Modem status interrupt
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_IIR_INPIN_SRC 0x30 /* Input pin change of state
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_IIR_XOFFI_SRC 0x10 /* Received Xoff */
|
|
|
|
#define SC16IS7XX_IIR_CTSRTS_SRC 0x20 /* nCTS,nRTS change of state
|
|
|
|
* from active (LOW)
|
|
|
|
* to inactive (HIGH)
|
|
|
|
*/
|
|
|
|
/* LCR register bits */
|
|
|
|
#define SC16IS7XX_LCR_LENGTH0_BIT (1 << 0) /* Word length bit 0 */
|
|
|
|
#define SC16IS7XX_LCR_LENGTH1_BIT (1 << 1) /* Word length bit 1
|
|
|
|
*
|
|
|
|
* Word length bits table:
|
|
|
|
* 00 -> 5 bit words
|
|
|
|
* 01 -> 6 bit words
|
|
|
|
* 10 -> 7 bit words
|
|
|
|
* 11 -> 8 bit words
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_LCR_STOPLEN_BIT (1 << 2) /* STOP length bit
|
|
|
|
*
|
|
|
|
* STOP length bit table:
|
|
|
|
* 0 -> 1 stop bit
|
|
|
|
* 1 -> 1-1.5 stop bits if
|
|
|
|
* word length is 5,
|
|
|
|
* 2 stop bits otherwise
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_LCR_PARITY_BIT (1 << 3) /* Parity bit enable */
|
|
|
|
#define SC16IS7XX_LCR_EVENPARITY_BIT (1 << 4) /* Even parity bit enable */
|
|
|
|
#define SC16IS7XX_LCR_FORCEPARITY_BIT (1 << 5) /* 9-bit multidrop parity */
|
|
|
|
#define SC16IS7XX_LCR_TXBREAK_BIT (1 << 6) /* TX break enable */
|
|
|
|
#define SC16IS7XX_LCR_DLAB_BIT (1 << 7) /* Divisor Latch enable */
|
|
|
|
#define SC16IS7XX_LCR_WORD_LEN_5 (0x00)
|
|
|
|
#define SC16IS7XX_LCR_WORD_LEN_6 (0x01)
|
|
|
|
#define SC16IS7XX_LCR_WORD_LEN_7 (0x02)
|
|
|
|
#define SC16IS7XX_LCR_WORD_LEN_8 (0x03)
|
|
|
|
#define SC16IS7XX_LCR_CONF_MODE_A SC16IS7XX_LCR_DLAB_BIT /* Special
|
|
|
|
* reg set */
|
|
|
|
#define SC16IS7XX_LCR_CONF_MODE_B 0xBF /* Enhanced
|
|
|
|
* reg set */
|
|
|
|
|
|
|
|
/* MCR register bits */
|
|
|
|
#define SC16IS7XX_MCR_DTR_BIT (1 << 0) /* DTR complement
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_MCR_RTS_BIT (1 << 1) /* RTS complement */
|
|
|
|
#define SC16IS7XX_MCR_TCRTLR_BIT (1 << 2) /* TCR/TLR register enable */
|
|
|
|
#define SC16IS7XX_MCR_LOOP_BIT (1 << 4) /* Enable loopback test mode */
|
|
|
|
#define SC16IS7XX_MCR_XONANY_BIT (1 << 5) /* Enable Xon Any
|
|
|
|
* - write enabled
|
|
|
|
* if (EFR[4] == 1)
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_MCR_IRDA_BIT (1 << 6) /* Enable IrDA mode
|
|
|
|
* - write enabled
|
|
|
|
* if (EFR[4] == 1)
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_MCR_CLKSEL_BIT (1 << 7) /* Divide clock by 4
|
|
|
|
* - write enabled
|
|
|
|
* if (EFR[4] == 1)
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* LSR register bits */
|
|
|
|
#define SC16IS7XX_LSR_DR_BIT (1 << 0) /* Receiver data ready */
|
|
|
|
#define SC16IS7XX_LSR_OE_BIT (1 << 1) /* Overrun Error */
|
|
|
|
#define SC16IS7XX_LSR_PE_BIT (1 << 2) /* Parity Error */
|
|
|
|
#define SC16IS7XX_LSR_FE_BIT (1 << 3) /* Frame Error */
|
|
|
|
#define SC16IS7XX_LSR_BI_BIT (1 << 4) /* Break Interrupt */
|
|
|
|
#define SC16IS7XX_LSR_BRK_ERROR_MASK 0x1E /* BI, FE, PE, OE bits */
|
|
|
|
#define SC16IS7XX_LSR_THRE_BIT (1 << 5) /* TX holding register empty */
|
|
|
|
#define SC16IS7XX_LSR_TEMT_BIT (1 << 6) /* Transmitter empty */
|
|
|
|
#define SC16IS7XX_LSR_FIFOE_BIT (1 << 7) /* Fifo Error */
|
|
|
|
|
|
|
|
/* MSR register bits */
|
|
|
|
#define SC16IS7XX_MSR_DCTS_BIT (1 << 0) /* Delta CTS Clear To Send */
|
|
|
|
#define SC16IS7XX_MSR_DDSR_BIT (1 << 1) /* Delta DSR Data Set Ready
|
|
|
|
* or (IO4)
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_MSR_DRI_BIT (1 << 2) /* Delta RI Ring Indicator
|
|
|
|
* or (IO7)
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_MSR_DCD_BIT (1 << 3) /* Delta CD Carrier Detect
|
|
|
|
* or (IO6)
|
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
2015-12-20 11:55:23 +07:00
|
|
|
#define SC16IS7XX_MSR_CTS_BIT (1 << 4) /* CTS */
|
|
|
|
#define SC16IS7XX_MSR_DSR_BIT (1 << 5) /* DSR (IO4)
|
2014-04-25 07:56:06 +07:00
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
2015-12-20 11:55:23 +07:00
|
|
|
#define SC16IS7XX_MSR_RI_BIT (1 << 6) /* RI (IO7)
|
2014-04-25 07:56:06 +07:00
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
2015-12-20 11:55:23 +07:00
|
|
|
#define SC16IS7XX_MSR_CD_BIT (1 << 7) /* CD (IO6)
|
2014-04-25 07:56:06 +07:00
|
|
|
* - only on 75x/76x
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_MSR_DELTA_MASK 0x0F /* Any of the delta bits! */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TCR register bits
|
|
|
|
* TCR trigger levels are available from 0 to 60 characters with a granularity
|
|
|
|
* of four.
|
|
|
|
* The programmer must program the TCR such that TCR[3:0] > TCR[7:4]. There is
|
|
|
|
* no built-in hardware check to make sure this condition is met. Also, the TCR
|
|
|
|
* must be programmed with this condition before auto RTS or software flow
|
|
|
|
* control is enabled to avoid spurious operation of the device.
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_TCR_RX_HALT(words) ((((words) / 4) & 0x0f) << 0)
|
|
|
|
#define SC16IS7XX_TCR_RX_RESUME(words) ((((words) / 4) & 0x0f) << 4)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TLR register bits
|
|
|
|
* If TLR[3:0] or TLR[7:4] are logical 0, the selectable trigger levels via the
|
|
|
|
* FIFO Control Register (FCR) are used for the transmit and receive FIFO
|
|
|
|
* trigger levels. Trigger levels from 4 characters to 60 characters are
|
|
|
|
* available with a granularity of four.
|
|
|
|
*
|
|
|
|
* When the trigger level setting in TLR is zero, the SC16IS740/750/760 uses the
|
|
|
|
* trigger level setting defined in FCR. If TLR has non-zero trigger level value
|
|
|
|
* the trigger level defined in FCR is discarded. This applies to both transmit
|
|
|
|
* FIFO and receive FIFO trigger level setting.
|
|
|
|
*
|
|
|
|
* When TLR is used for RX trigger level control, FCR[7:6] should be left at the
|
|
|
|
* default state, that is, '00'.
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_TLR_TX_TRIGGER(words) ((((words) / 4) & 0x0f) << 0)
|
|
|
|
#define SC16IS7XX_TLR_RX_TRIGGER(words) ((((words) / 4) & 0x0f) << 4)
|
|
|
|
|
|
|
|
/* IOControl register bits (Only 750/760) */
|
|
|
|
#define SC16IS7XX_IOCONTROL_LATCH_BIT (1 << 0) /* Enable input latching */
|
2015-12-20 11:55:23 +07:00
|
|
|
#define SC16IS7XX_IOCONTROL_MODEM_BIT (1 << 1) /* Enable GPIO[7:4] as modem pins */
|
2014-04-25 07:56:06 +07:00
|
|
|
#define SC16IS7XX_IOCONTROL_SRESET_BIT (1 << 3) /* Software Reset */
|
|
|
|
|
|
|
|
/* EFCR register bits */
|
|
|
|
#define SC16IS7XX_EFCR_9BIT_MODE_BIT (1 << 0) /* Enable 9-bit or Multidrop
|
|
|
|
* mode (RS485) */
|
|
|
|
#define SC16IS7XX_EFCR_RXDISABLE_BIT (1 << 1) /* Disable receiver */
|
|
|
|
#define SC16IS7XX_EFCR_TXDISABLE_BIT (1 << 2) /* Disable transmitter */
|
|
|
|
#define SC16IS7XX_EFCR_AUTO_RS485_BIT (1 << 4) /* Auto RS485 RTS direction */
|
|
|
|
#define SC16IS7XX_EFCR_RTS_INVERT_BIT (1 << 5) /* RTS output inversion */
|
|
|
|
#define SC16IS7XX_EFCR_IRDA_MODE_BIT (1 << 7) /* IrDA mode
|
|
|
|
* 0 = rate upto 115.2 kbit/s
|
|
|
|
* - Only 750/760
|
|
|
|
* 1 = rate upto 1.152 Mbit/s
|
|
|
|
* - Only 760
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* EFR register bits */
|
|
|
|
#define SC16IS7XX_EFR_AUTORTS_BIT (1 << 6) /* Auto RTS flow ctrl enable */
|
|
|
|
#define SC16IS7XX_EFR_AUTOCTS_BIT (1 << 7) /* Auto CTS flow ctrl enable */
|
|
|
|
#define SC16IS7XX_EFR_XOFF2_DETECT_BIT (1 << 5) /* Enable Xoff2 detection */
|
|
|
|
#define SC16IS7XX_EFR_ENABLE_BIT (1 << 4) /* Enable enhanced functions
|
|
|
|
* and writing to IER[7:4],
|
|
|
|
* FCR[5:4], MCR[7:5]
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_EFR_SWFLOW3_BIT (1 << 3) /* SWFLOW bit 3 */
|
|
|
|
#define SC16IS7XX_EFR_SWFLOW2_BIT (1 << 2) /* SWFLOW bit 2
|
|
|
|
*
|
|
|
|
* SWFLOW bits 3 & 2 table:
|
|
|
|
* 00 -> no transmitter flow
|
|
|
|
* control
|
|
|
|
* 01 -> transmitter generates
|
|
|
|
* XON2 and XOFF2
|
|
|
|
* 10 -> transmitter generates
|
|
|
|
* XON1 and XOFF1
|
|
|
|
* 11 -> transmitter generates
|
|
|
|
* XON1, XON2, XOFF1 and
|
|
|
|
* XOFF2
|
|
|
|
*/
|
|
|
|
#define SC16IS7XX_EFR_SWFLOW1_BIT (1 << 1) /* SWFLOW bit 2 */
|
|
|
|
#define SC16IS7XX_EFR_SWFLOW0_BIT (1 << 0) /* SWFLOW bit 3
|
|
|
|
*
|
|
|
|
* SWFLOW bits 3 & 2 table:
|
|
|
|
* 00 -> no received flow
|
|
|
|
* control
|
|
|
|
* 01 -> receiver compares
|
|
|
|
* XON2 and XOFF2
|
|
|
|
* 10 -> receiver compares
|
|
|
|
* XON1 and XOFF1
|
|
|
|
* 11 -> receiver compares
|
|
|
|
* XON1, XON2, XOFF1 and
|
|
|
|
* XOFF2
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Misc definitions */
|
|
|
|
#define SC16IS7XX_FIFO_SIZE (64)
|
|
|
|
#define SC16IS7XX_REG_SHIFT 2
|
|
|
|
|
|
|
|
struct sc16is7xx_devtype {
|
|
|
|
char name[10];
|
|
|
|
int nr_gpio;
|
|
|
|
int nr_uart;
|
|
|
|
};
|
|
|
|
|
2015-05-30 02:20:31 +07:00
|
|
|
#define SC16IS7XX_RECONF_MD (1 << 0)
|
2015-05-30 02:20:32 +07:00
|
|
|
#define SC16IS7XX_RECONF_IER (1 << 1)
|
2015-05-30 02:20:33 +07:00
|
|
|
#define SC16IS7XX_RECONF_RS485 (1 << 2)
|
2015-05-30 02:20:31 +07:00
|
|
|
|
|
|
|
struct sc16is7xx_one_config {
|
|
|
|
unsigned int flags;
|
2015-05-30 02:20:32 +07:00
|
|
|
u8 ier_clear;
|
2015-05-30 02:20:31 +07:00
|
|
|
};
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
struct sc16is7xx_one {
|
|
|
|
struct uart_port port;
|
2015-07-31 19:44:22 +07:00
|
|
|
u8 line;
|
2015-05-30 02:20:29 +07:00
|
|
|
struct kthread_work tx_work;
|
2015-05-30 02:20:31 +07:00
|
|
|
struct kthread_work reg_work;
|
|
|
|
struct sc16is7xx_one_config config;
|
2014-04-25 07:56:06 +07:00
|
|
|
};
|
|
|
|
|
|
|
|
struct sc16is7xx_port {
|
2015-07-31 19:44:24 +07:00
|
|
|
const struct sc16is7xx_devtype *devtype;
|
2014-04-25 07:56:06 +07:00
|
|
|
struct regmap *regmap;
|
|
|
|
struct clk *clk;
|
|
|
|
#ifdef CONFIG_GPIOLIB
|
|
|
|
struct gpio_chip gpio;
|
|
|
|
#endif
|
2014-04-26 07:11:07 +07:00
|
|
|
unsigned char buf[SC16IS7XX_FIFO_SIZE];
|
2015-05-30 02:20:29 +07:00
|
|
|
struct kthread_worker kworker;
|
|
|
|
struct task_struct *kworker_task;
|
|
|
|
struct kthread_work irq_work;
|
2018-09-12 21:31:56 +07:00
|
|
|
struct mutex efr_lock;
|
2014-04-25 07:56:06 +07:00
|
|
|
struct sc16is7xx_one p[0];
|
|
|
|
};
|
|
|
|
|
2015-07-31 19:44:23 +07:00
|
|
|
static unsigned long sc16is7xx_lines;
|
|
|
|
|
|
|
|
static struct uart_driver sc16is7xx_uart = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.dev_name = "ttySC",
|
|
|
|
.nr = SC16IS7XX_MAX_DEVS,
|
|
|
|
};
|
|
|
|
|
2015-05-30 02:20:29 +07:00
|
|
|
#define to_sc16is7xx_port(p,e) ((container_of((p), struct sc16is7xx_port, e)))
|
2014-04-25 07:56:06 +07:00
|
|
|
#define to_sc16is7xx_one(p,e) ((container_of((p), struct sc16is7xx_one, e)))
|
|
|
|
|
2015-07-31 19:44:22 +07:00
|
|
|
static int sc16is7xx_line(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
|
|
|
|
|
|
|
|
return one->line;
|
|
|
|
}
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
static u8 sc16is7xx_port_read(struct uart_port *port, u8 reg)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
unsigned int val = 0;
|
2015-07-31 19:44:22 +07:00
|
|
|
const u8 line = sc16is7xx_line(port);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2015-07-31 19:44:22 +07:00
|
|
|
regmap_read(s->regmap, (reg << SC16IS7XX_REG_SHIFT) | line, &val);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_port_write(struct uart_port *port, u8 reg, u8 val)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
2015-07-31 19:44:22 +07:00
|
|
|
const u8 line = sc16is7xx_line(port);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2015-07-31 19:44:22 +07:00
|
|
|
regmap_write(s->regmap, (reg << SC16IS7XX_REG_SHIFT) | line, val);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
2015-06-13 18:40:20 +07:00
|
|
|
static void sc16is7xx_fifo_read(struct uart_port *port, unsigned int rxlen)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
2015-07-31 19:44:22 +07:00
|
|
|
const u8 line = sc16is7xx_line(port);
|
|
|
|
u8 addr = (SC16IS7XX_RHR_REG << SC16IS7XX_REG_SHIFT) | line;
|
2015-06-13 18:40:20 +07:00
|
|
|
|
|
|
|
regcache_cache_bypass(s->regmap, true);
|
|
|
|
regmap_raw_read(s->regmap, addr, s->buf, rxlen);
|
|
|
|
regcache_cache_bypass(s->regmap, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_fifo_write(struct uart_port *port, u8 to_send)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
2015-07-31 19:44:22 +07:00
|
|
|
const u8 line = sc16is7xx_line(port);
|
|
|
|
u8 addr = (SC16IS7XX_THR_REG << SC16IS7XX_REG_SHIFT) | line;
|
2015-06-13 18:40:20 +07:00
|
|
|
|
sc16is7xx: Fix TX buffer overrun caused by wrong tx fifo level read-out
We found that our sc16is7xx on spi reported a TX fifo free space value
(TXLVL_REG) of 255 ocassionally, which is obviously wrong, with a
64 byte fifo and caused a buffer overrun and a kernel crash.
To trigger this, a large write to the tty is sufficient. The fifo fills,
TXLVL_REG reads zero, but the handle_tx function does a zero-data-length
write to the TX fifo anyways through sc16is7xx_fifo_write. The next
TXLVL_REG read then yields 255, for unknown reasons. A subsequent read
is ok.
Prevent zero-data-length writes if the TX fifo is full, because they are
pointless, and because they trigger wrong TXLVL read-outs.
Furthermore, prevent a TX buffer overrun if the peripheral reports values
larger than the buffer size and thus, don't allow the peripheral to crash
the kernel.
Signed-off-by: Florian Achleitner <achleitner.florian@fronius.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-11-18 15:04:12 +07:00
|
|
|
/*
|
|
|
|
* Don't send zero-length data, at least on SPI it confuses the chip
|
|
|
|
* delivering wrong TXLVL data.
|
|
|
|
*/
|
|
|
|
if (unlikely(!to_send))
|
|
|
|
return;
|
|
|
|
|
2015-06-13 18:40:20 +07:00
|
|
|
regcache_cache_bypass(s->regmap, true);
|
|
|
|
regmap_raw_write(s->regmap, addr, s->buf, to_send);
|
|
|
|
regcache_cache_bypass(s->regmap, false);
|
|
|
|
}
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
static void sc16is7xx_port_update(struct uart_port *port, u8 reg,
|
|
|
|
u8 mask, u8 val)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
2015-07-31 19:44:22 +07:00
|
|
|
const u8 line = sc16is7xx_line(port);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2015-07-31 19:44:22 +07:00
|
|
|
regmap_update_bits(s->regmap, (reg << SC16IS7XX_REG_SHIFT) | line,
|
2014-04-25 07:56:06 +07:00
|
|
|
mask, val);
|
|
|
|
}
|
|
|
|
|
2015-07-31 19:44:23 +07:00
|
|
|
static int sc16is7xx_alloc_line(void)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
BUILD_BUG_ON(SC16IS7XX_MAX_DEVS > BITS_PER_LONG);
|
|
|
|
|
|
|
|
for (i = 0; i < SC16IS7XX_MAX_DEVS; i++)
|
|
|
|
if (!test_and_set_bit(i, &sc16is7xx_lines))
|
|
|
|
break;
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
static void sc16is7xx_power(struct uart_port *port, int on)
|
|
|
|
{
|
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_IER_REG,
|
|
|
|
SC16IS7XX_IER_SLEEP_BIT,
|
|
|
|
on ? 0 : SC16IS7XX_IER_SLEEP_BIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct sc16is7xx_devtype sc16is74x_devtype = {
|
|
|
|
.name = "SC16IS74X",
|
|
|
|
.nr_gpio = 0,
|
|
|
|
.nr_uart = 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct sc16is7xx_devtype sc16is750_devtype = {
|
|
|
|
.name = "SC16IS750",
|
|
|
|
.nr_gpio = 8,
|
|
|
|
.nr_uart = 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct sc16is7xx_devtype sc16is752_devtype = {
|
|
|
|
.name = "SC16IS752",
|
|
|
|
.nr_gpio = 8,
|
|
|
|
.nr_uart = 2,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct sc16is7xx_devtype sc16is760_devtype = {
|
|
|
|
.name = "SC16IS760",
|
|
|
|
.nr_gpio = 8,
|
|
|
|
.nr_uart = 1,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct sc16is7xx_devtype sc16is762_devtype = {
|
|
|
|
.name = "SC16IS762",
|
|
|
|
.nr_gpio = 8,
|
|
|
|
.nr_uart = 2,
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool sc16is7xx_regmap_volatile(struct device *dev, unsigned int reg)
|
|
|
|
{
|
|
|
|
switch (reg >> SC16IS7XX_REG_SHIFT) {
|
|
|
|
case SC16IS7XX_RHR_REG:
|
|
|
|
case SC16IS7XX_IIR_REG:
|
|
|
|
case SC16IS7XX_LSR_REG:
|
|
|
|
case SC16IS7XX_MSR_REG:
|
|
|
|
case SC16IS7XX_TXLVL_REG:
|
|
|
|
case SC16IS7XX_RXLVL_REG:
|
|
|
|
case SC16IS7XX_IOSTATE_REG:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool sc16is7xx_regmap_precious(struct device *dev, unsigned int reg)
|
|
|
|
{
|
|
|
|
switch (reg >> SC16IS7XX_REG_SHIFT) {
|
|
|
|
case SC16IS7XX_RHR_REG:
|
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_set_baud(struct uart_port *port, int baud)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
u8 lcr;
|
|
|
|
u8 prescaler = 0;
|
|
|
|
unsigned long clk = port->uartclk, div = clk / 16 / baud;
|
|
|
|
|
|
|
|
if (div > 0xffff) {
|
|
|
|
prescaler = SC16IS7XX_MCR_CLKSEL_BIT;
|
|
|
|
div /= 4;
|
|
|
|
}
|
|
|
|
|
2018-09-12 21:31:56 +07:00
|
|
|
/* In an amazing feat of design, the Enhanced Features Register shares
|
|
|
|
* the address of the Interrupt Identification Register, and is
|
|
|
|
* switched in by writing a magic value (0xbf) to the Line Control
|
|
|
|
* Register. Any interrupt firing during this time will see the EFR
|
|
|
|
* where it expects the IIR to be, leading to "Unexpected interrupt"
|
|
|
|
* messages.
|
|
|
|
*
|
|
|
|
* Prevent this possibility by claiming a mutex while accessing the
|
|
|
|
* EFR, and claiming the same mutex from within the interrupt handler.
|
|
|
|
* This is similar to disabling the interrupt, but that doesn't work
|
|
|
|
* because the bulk of the interrupt processing is run as a workqueue
|
|
|
|
* job in thread context.
|
|
|
|
*/
|
|
|
|
mutex_lock(&s->efr_lock);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
lcr = sc16is7xx_port_read(port, SC16IS7XX_LCR_REG);
|
|
|
|
|
|
|
|
/* Open the LCR divisors for configuration */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
|
|
|
|
SC16IS7XX_LCR_CONF_MODE_B);
|
|
|
|
|
|
|
|
/* Enable enhanced features */
|
|
|
|
regcache_cache_bypass(s->regmap, true);
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_EFR_REG,
|
|
|
|
SC16IS7XX_EFR_ENABLE_BIT);
|
|
|
|
regcache_cache_bypass(s->regmap, false);
|
|
|
|
|
|
|
|
/* Put LCR back to the normal mode */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
|
|
|
|
|
2018-09-12 21:31:56 +07:00
|
|
|
mutex_unlock(&s->efr_lock);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_MCR_REG,
|
|
|
|
SC16IS7XX_MCR_CLKSEL_BIT,
|
|
|
|
prescaler);
|
|
|
|
|
|
|
|
/* Open the LCR divisors for configuration */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
|
|
|
|
SC16IS7XX_LCR_CONF_MODE_A);
|
|
|
|
|
|
|
|
/* Write the new divisor */
|
|
|
|
regcache_cache_bypass(s->regmap, true);
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_DLH_REG, div / 256);
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_DLL_REG, div % 256);
|
|
|
|
regcache_cache_bypass(s->regmap, false);
|
|
|
|
|
|
|
|
/* Put LCR back to the normal mode */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
|
|
|
|
|
|
|
|
return DIV_ROUND_CLOSEST(clk / 16, div);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_handle_rx(struct uart_port *port, unsigned int rxlen,
|
|
|
|
unsigned int iir)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
unsigned int lsr = 0, ch, flag, bytes_read, i;
|
|
|
|
bool read_lsr = (iir == SC16IS7XX_IIR_RLSE_SRC) ? true : false;
|
|
|
|
|
2014-04-26 07:11:07 +07:00
|
|
|
if (unlikely(rxlen >= sizeof(s->buf))) {
|
2014-04-25 07:56:06 +07:00
|
|
|
dev_warn_ratelimited(port->dev,
|
2015-07-31 19:44:22 +07:00
|
|
|
"ttySC%i: Possible RX FIFO overrun: %d\n",
|
2014-04-25 07:56:06 +07:00
|
|
|
port->line, rxlen);
|
|
|
|
port->icount.buf_overrun++;
|
|
|
|
/* Ensure sanity of RX level */
|
2014-04-26 07:11:07 +07:00
|
|
|
rxlen = sizeof(s->buf);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
while (rxlen) {
|
|
|
|
/* Only read lsr if there are possible errors in FIFO */
|
|
|
|
if (read_lsr) {
|
|
|
|
lsr = sc16is7xx_port_read(port, SC16IS7XX_LSR_REG);
|
|
|
|
if (!(lsr & SC16IS7XX_LSR_FIFOE_BIT))
|
|
|
|
read_lsr = false; /* No errors left in FIFO */
|
|
|
|
} else
|
|
|
|
lsr = 0;
|
|
|
|
|
|
|
|
if (read_lsr) {
|
2014-04-26 07:11:07 +07:00
|
|
|
s->buf[0] = sc16is7xx_port_read(port, SC16IS7XX_RHR_REG);
|
2014-04-25 07:56:06 +07:00
|
|
|
bytes_read = 1;
|
|
|
|
} else {
|
2015-06-13 18:40:20 +07:00
|
|
|
sc16is7xx_fifo_read(port, rxlen);
|
2014-04-25 07:56:06 +07:00
|
|
|
bytes_read = rxlen;
|
|
|
|
}
|
|
|
|
|
|
|
|
lsr &= SC16IS7XX_LSR_BRK_ERROR_MASK;
|
|
|
|
|
|
|
|
port->icount.rx++;
|
|
|
|
flag = TTY_NORMAL;
|
|
|
|
|
|
|
|
if (unlikely(lsr)) {
|
|
|
|
if (lsr & SC16IS7XX_LSR_BI_BIT) {
|
|
|
|
port->icount.brk++;
|
|
|
|
if (uart_handle_break(port))
|
|
|
|
continue;
|
|
|
|
} else if (lsr & SC16IS7XX_LSR_PE_BIT)
|
|
|
|
port->icount.parity++;
|
|
|
|
else if (lsr & SC16IS7XX_LSR_FE_BIT)
|
|
|
|
port->icount.frame++;
|
|
|
|
else if (lsr & SC16IS7XX_LSR_OE_BIT)
|
|
|
|
port->icount.overrun++;
|
|
|
|
|
|
|
|
lsr &= port->read_status_mask;
|
|
|
|
if (lsr & SC16IS7XX_LSR_BI_BIT)
|
|
|
|
flag = TTY_BREAK;
|
|
|
|
else if (lsr & SC16IS7XX_LSR_PE_BIT)
|
|
|
|
flag = TTY_PARITY;
|
|
|
|
else if (lsr & SC16IS7XX_LSR_FE_BIT)
|
|
|
|
flag = TTY_FRAME;
|
|
|
|
else if (lsr & SC16IS7XX_LSR_OE_BIT)
|
|
|
|
flag = TTY_OVERRUN;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < bytes_read; ++i) {
|
2014-04-26 07:11:07 +07:00
|
|
|
ch = s->buf[i];
|
2014-04-25 07:56:06 +07:00
|
|
|
if (uart_handle_sysrq_char(port, ch))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (lsr & port->ignore_status_mask)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
uart_insert_char(port, lsr, SC16IS7XX_LSR_OE_BIT, ch,
|
|
|
|
flag);
|
|
|
|
}
|
|
|
|
rxlen -= bytes_read;
|
|
|
|
}
|
|
|
|
|
|
|
|
tty_flip_buffer_push(&port->state->port);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_handle_tx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
struct circ_buf *xmit = &port->state->xmit;
|
|
|
|
unsigned int txlen, to_send, i;
|
|
|
|
|
|
|
|
if (unlikely(port->x_char)) {
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_THR_REG, port->x_char);
|
|
|
|
port->icount.tx++;
|
|
|
|
port->x_char = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uart_circ_empty(xmit) || uart_tx_stopped(port))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Get length of data pending in circular buffer */
|
|
|
|
to_send = uart_circ_chars_pending(xmit);
|
|
|
|
if (likely(to_send)) {
|
|
|
|
/* Limit to size of TX FIFO */
|
|
|
|
txlen = sc16is7xx_port_read(port, SC16IS7XX_TXLVL_REG);
|
sc16is7xx: Fix TX buffer overrun caused by wrong tx fifo level read-out
We found that our sc16is7xx on spi reported a TX fifo free space value
(TXLVL_REG) of 255 ocassionally, which is obviously wrong, with a
64 byte fifo and caused a buffer overrun and a kernel crash.
To trigger this, a large write to the tty is sufficient. The fifo fills,
TXLVL_REG reads zero, but the handle_tx function does a zero-data-length
write to the TX fifo anyways through sc16is7xx_fifo_write. The next
TXLVL_REG read then yields 255, for unknown reasons. A subsequent read
is ok.
Prevent zero-data-length writes if the TX fifo is full, because they are
pointless, and because they trigger wrong TXLVL read-outs.
Furthermore, prevent a TX buffer overrun if the peripheral reports values
larger than the buffer size and thus, don't allow the peripheral to crash
the kernel.
Signed-off-by: Florian Achleitner <achleitner.florian@fronius.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2015-11-18 15:04:12 +07:00
|
|
|
if (txlen > SC16IS7XX_FIFO_SIZE) {
|
|
|
|
dev_err_ratelimited(port->dev,
|
|
|
|
"chip reports %d free bytes in TX fifo, but it only has %d",
|
|
|
|
txlen, SC16IS7XX_FIFO_SIZE);
|
|
|
|
txlen = 0;
|
|
|
|
}
|
2014-04-25 07:56:06 +07:00
|
|
|
to_send = (to_send > txlen) ? txlen : to_send;
|
|
|
|
|
|
|
|
/* Add data to send */
|
|
|
|
port->icount.tx += to_send;
|
|
|
|
|
|
|
|
/* Convert to linear buffer */
|
|
|
|
for (i = 0; i < to_send; ++i) {
|
2014-04-26 07:11:07 +07:00
|
|
|
s->buf[i] = xmit->buf[xmit->tail];
|
2014-04-25 07:56:06 +07:00
|
|
|
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
|
|
|
|
}
|
2015-06-13 18:40:20 +07:00
|
|
|
|
|
|
|
sc16is7xx_fifo_write(port, to_send);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
|
|
|
uart_write_wakeup(port);
|
|
|
|
}
|
|
|
|
|
sc16is7xx: Fix for multi-channel stall
The SC16IS752 is a dual-channel device. The two channels are largely
independent, but the IRQ signals are wired together as an open-drain,
active low signal which will be driven low while either of the
channels requires attention, which can be for significant periods of
time until operations complete and the interrupt can be acknowledged.
In that respect it is should be treated as a true level-sensitive IRQ.
The kernel, however, needs to be able to exit interrupt context in
order to use I2C or SPI to access the device registers (which may
involve sleeping). Therefore the interrupt needs to be masked out or
paused in some way.
The usual way to manage sleeping from within an interrupt handler
is to use a threaded interrupt handler - a regular interrupt routine
does the minimum amount of work needed to triage the interrupt before
waking the interrupt service thread. If the threaded IRQ is marked as
IRQF_ONESHOT the kernel will automatically mask out the interrupt
until the thread runs to completion. The sc16is7xx driver used to
use a threaded IRQ, but a patch switched to using a kthread_worker
in order to set realtime priorities on the handler thread and for
other optimisations. The end result is non-threaded IRQ that
schedules some work then returns IRQ_HANDLED, making the kernel
think that all IRQ processing has completed.
The work-around to prevent a constant stream of interrupts is to
mark the interrupt as edge-sensitive rather than level-sensitive,
but interpreting an active-low source as a falling-edge source
requires care to prevent a total cessation of interrupts. Whereas
an edge-triggering source will generate a new edge for every interrupt
condition a level-triggering source will keep the signal at the
interrupting level until it no longer requires attention; in other
words, the host won't see another edge until all interrupt conditions
are cleared. It is therefore vital that the interrupt handler does not
exit with an outstanding interrupt condition, otherwise the kernel
will not receive another interrupt unless some other operation causes
the interrupt state on the device to be cleared.
The existing sc16is7xx driver has a very simple interrupt "thread"
(kthread_work job) that processes interrupts on each channel in turn
until there are no more. If both channels are active and the first
channel starts interrupting while the handler for the second channel
is running then it will not be detected and an IRQ stall ensues. This
could be handled easily if there was a shared IRQ status register, or
a convenient way to determine if the IRQ had been deasserted for any
length of time, but both appear to be lacking.
Avoid this problem (or at least make it much less likely to happen)
by reducing the granularity of per-channel interrupt processing
to one condition per iteration, only exiting the overall loop when
both channels are no longer interrupting.
Signed-off-by: Phil Elwell <phil@raspberrypi.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-09-12 21:31:55 +07:00
|
|
|
static bool sc16is7xx_port_irq(struct sc16is7xx_port *s, int portno)
|
2014-04-25 07:56:06 +07:00
|
|
|
{
|
|
|
|
struct uart_port *port = &s->p[portno].port;
|
|
|
|
|
|
|
|
do {
|
2016-03-05 21:09:05 +07:00
|
|
|
unsigned int iir, rxlen;
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
iir = sc16is7xx_port_read(port, SC16IS7XX_IIR_REG);
|
|
|
|
if (iir & SC16IS7XX_IIR_NO_INT_BIT)
|
sc16is7xx: Fix for multi-channel stall
The SC16IS752 is a dual-channel device. The two channels are largely
independent, but the IRQ signals are wired together as an open-drain,
active low signal which will be driven low while either of the
channels requires attention, which can be for significant periods of
time until operations complete and the interrupt can be acknowledged.
In that respect it is should be treated as a true level-sensitive IRQ.
The kernel, however, needs to be able to exit interrupt context in
order to use I2C or SPI to access the device registers (which may
involve sleeping). Therefore the interrupt needs to be masked out or
paused in some way.
The usual way to manage sleeping from within an interrupt handler
is to use a threaded interrupt handler - a regular interrupt routine
does the minimum amount of work needed to triage the interrupt before
waking the interrupt service thread. If the threaded IRQ is marked as
IRQF_ONESHOT the kernel will automatically mask out the interrupt
until the thread runs to completion. The sc16is7xx driver used to
use a threaded IRQ, but a patch switched to using a kthread_worker
in order to set realtime priorities on the handler thread and for
other optimisations. The end result is non-threaded IRQ that
schedules some work then returns IRQ_HANDLED, making the kernel
think that all IRQ processing has completed.
The work-around to prevent a constant stream of interrupts is to
mark the interrupt as edge-sensitive rather than level-sensitive,
but interpreting an active-low source as a falling-edge source
requires care to prevent a total cessation of interrupts. Whereas
an edge-triggering source will generate a new edge for every interrupt
condition a level-triggering source will keep the signal at the
interrupting level until it no longer requires attention; in other
words, the host won't see another edge until all interrupt conditions
are cleared. It is therefore vital that the interrupt handler does not
exit with an outstanding interrupt condition, otherwise the kernel
will not receive another interrupt unless some other operation causes
the interrupt state on the device to be cleared.
The existing sc16is7xx driver has a very simple interrupt "thread"
(kthread_work job) that processes interrupts on each channel in turn
until there are no more. If both channels are active and the first
channel starts interrupting while the handler for the second channel
is running then it will not be detected and an IRQ stall ensues. This
could be handled easily if there was a shared IRQ status register, or
a convenient way to determine if the IRQ had been deasserted for any
length of time, but both appear to be lacking.
Avoid this problem (or at least make it much less likely to happen)
by reducing the granularity of per-channel interrupt processing
to one condition per iteration, only exiting the overall loop when
both channels are no longer interrupting.
Signed-off-by: Phil Elwell <phil@raspberrypi.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-09-12 21:31:55 +07:00
|
|
|
return false;
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
iir &= SC16IS7XX_IIR_ID_MASK;
|
|
|
|
|
|
|
|
switch (iir) {
|
|
|
|
case SC16IS7XX_IIR_RDI_SRC:
|
|
|
|
case SC16IS7XX_IIR_RLSE_SRC:
|
|
|
|
case SC16IS7XX_IIR_RTOI_SRC:
|
|
|
|
case SC16IS7XX_IIR_XOFFI_SRC:
|
|
|
|
rxlen = sc16is7xx_port_read(port, SC16IS7XX_RXLVL_REG);
|
|
|
|
if (rxlen)
|
|
|
|
sc16is7xx_handle_rx(port, rxlen, iir);
|
|
|
|
break;
|
|
|
|
case SC16IS7XX_IIR_THRI_SRC:
|
|
|
|
sc16is7xx_handle_tx(port);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
dev_err_ratelimited(port->dev,
|
2015-07-31 19:44:22 +07:00
|
|
|
"ttySC%i: Unexpected interrupt: %x",
|
2014-04-25 07:56:06 +07:00
|
|
|
port->line, iir);
|
|
|
|
break;
|
|
|
|
}
|
sc16is7xx: Fix for multi-channel stall
The SC16IS752 is a dual-channel device. The two channels are largely
independent, but the IRQ signals are wired together as an open-drain,
active low signal which will be driven low while either of the
channels requires attention, which can be for significant periods of
time until operations complete and the interrupt can be acknowledged.
In that respect it is should be treated as a true level-sensitive IRQ.
The kernel, however, needs to be able to exit interrupt context in
order to use I2C or SPI to access the device registers (which may
involve sleeping). Therefore the interrupt needs to be masked out or
paused in some way.
The usual way to manage sleeping from within an interrupt handler
is to use a threaded interrupt handler - a regular interrupt routine
does the minimum amount of work needed to triage the interrupt before
waking the interrupt service thread. If the threaded IRQ is marked as
IRQF_ONESHOT the kernel will automatically mask out the interrupt
until the thread runs to completion. The sc16is7xx driver used to
use a threaded IRQ, but a patch switched to using a kthread_worker
in order to set realtime priorities on the handler thread and for
other optimisations. The end result is non-threaded IRQ that
schedules some work then returns IRQ_HANDLED, making the kernel
think that all IRQ processing has completed.
The work-around to prevent a constant stream of interrupts is to
mark the interrupt as edge-sensitive rather than level-sensitive,
but interpreting an active-low source as a falling-edge source
requires care to prevent a total cessation of interrupts. Whereas
an edge-triggering source will generate a new edge for every interrupt
condition a level-triggering source will keep the signal at the
interrupting level until it no longer requires attention; in other
words, the host won't see another edge until all interrupt conditions
are cleared. It is therefore vital that the interrupt handler does not
exit with an outstanding interrupt condition, otherwise the kernel
will not receive another interrupt unless some other operation causes
the interrupt state on the device to be cleared.
The existing sc16is7xx driver has a very simple interrupt "thread"
(kthread_work job) that processes interrupts on each channel in turn
until there are no more. If both channels are active and the first
channel starts interrupting while the handler for the second channel
is running then it will not be detected and an IRQ stall ensues. This
could be handled easily if there was a shared IRQ status register, or
a convenient way to determine if the IRQ had been deasserted for any
length of time, but both appear to be lacking.
Avoid this problem (or at least make it much less likely to happen)
by reducing the granularity of per-channel interrupt processing
to one condition per iteration, only exiting the overall loop when
both channels are no longer interrupting.
Signed-off-by: Phil Elwell <phil@raspberrypi.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-09-12 21:31:55 +07:00
|
|
|
} while (0);
|
|
|
|
return true;
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
2015-05-30 02:20:29 +07:00
|
|
|
static void sc16is7xx_ist(struct kthread_work *ws)
|
2014-04-25 07:56:06 +07:00
|
|
|
{
|
2015-05-30 02:20:29 +07:00
|
|
|
struct sc16is7xx_port *s = to_sc16is7xx_port(ws, irq_work);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2018-09-12 21:31:56 +07:00
|
|
|
mutex_lock(&s->efr_lock);
|
|
|
|
|
sc16is7xx: Fix for multi-channel stall
The SC16IS752 is a dual-channel device. The two channels are largely
independent, but the IRQ signals are wired together as an open-drain,
active low signal which will be driven low while either of the
channels requires attention, which can be for significant periods of
time until operations complete and the interrupt can be acknowledged.
In that respect it is should be treated as a true level-sensitive IRQ.
The kernel, however, needs to be able to exit interrupt context in
order to use I2C or SPI to access the device registers (which may
involve sleeping). Therefore the interrupt needs to be masked out or
paused in some way.
The usual way to manage sleeping from within an interrupt handler
is to use a threaded interrupt handler - a regular interrupt routine
does the minimum amount of work needed to triage the interrupt before
waking the interrupt service thread. If the threaded IRQ is marked as
IRQF_ONESHOT the kernel will automatically mask out the interrupt
until the thread runs to completion. The sc16is7xx driver used to
use a threaded IRQ, but a patch switched to using a kthread_worker
in order to set realtime priorities on the handler thread and for
other optimisations. The end result is non-threaded IRQ that
schedules some work then returns IRQ_HANDLED, making the kernel
think that all IRQ processing has completed.
The work-around to prevent a constant stream of interrupts is to
mark the interrupt as edge-sensitive rather than level-sensitive,
but interpreting an active-low source as a falling-edge source
requires care to prevent a total cessation of interrupts. Whereas
an edge-triggering source will generate a new edge for every interrupt
condition a level-triggering source will keep the signal at the
interrupting level until it no longer requires attention; in other
words, the host won't see another edge until all interrupt conditions
are cleared. It is therefore vital that the interrupt handler does not
exit with an outstanding interrupt condition, otherwise the kernel
will not receive another interrupt unless some other operation causes
the interrupt state on the device to be cleared.
The existing sc16is7xx driver has a very simple interrupt "thread"
(kthread_work job) that processes interrupts on each channel in turn
until there are no more. If both channels are active and the first
channel starts interrupting while the handler for the second channel
is running then it will not be detected and an IRQ stall ensues. This
could be handled easily if there was a shared IRQ status register, or
a convenient way to determine if the IRQ had been deasserted for any
length of time, but both appear to be lacking.
Avoid this problem (or at least make it much less likely to happen)
by reducing the granularity of per-channel interrupt processing
to one condition per iteration, only exiting the overall loop when
both channels are no longer interrupting.
Signed-off-by: Phil Elwell <phil@raspberrypi.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-09-12 21:31:55 +07:00
|
|
|
while (1) {
|
|
|
|
bool keep_polling = false;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < s->devtype->nr_uart; ++i)
|
|
|
|
keep_polling |= sc16is7xx_port_irq(s, i);
|
|
|
|
if (!keep_polling)
|
|
|
|
break;
|
|
|
|
}
|
2018-09-12 21:31:56 +07:00
|
|
|
|
|
|
|
mutex_unlock(&s->efr_lock);
|
2015-05-30 02:20:29 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t sc16is7xx_irq(int irq, void *dev_id)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = (struct sc16is7xx_port *)dev_id;
|
|
|
|
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_queue_work(&s->kworker, &s->irq_work);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2015-05-30 02:20:29 +07:00
|
|
|
static void sc16is7xx_tx_proc(struct kthread_work *ws)
|
2014-04-25 07:56:06 +07:00
|
|
|
{
|
2015-05-30 02:20:30 +07:00
|
|
|
struct uart_port *port = &(to_sc16is7xx_one(ws, tx_work)->port);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2015-05-30 02:20:30 +07:00
|
|
|
if ((port->rs485.flags & SER_RS485_ENABLED) &&
|
|
|
|
(port->rs485.delay_rts_before_send > 0))
|
|
|
|
msleep(port->rs485.delay_rts_before_send);
|
|
|
|
|
|
|
|
sc16is7xx_handle_tx(port);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
2015-05-30 02:20:33 +07:00
|
|
|
static void sc16is7xx_reconf_rs485(struct uart_port *port)
|
|
|
|
{
|
|
|
|
const u32 mask = SC16IS7XX_EFCR_AUTO_RS485_BIT |
|
|
|
|
SC16IS7XX_EFCR_RTS_INVERT_BIT;
|
|
|
|
u32 efcr = 0;
|
|
|
|
struct serial_rs485 *rs485 = &port->rs485;
|
|
|
|
unsigned long irqflags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&port->lock, irqflags);
|
|
|
|
if (rs485->flags & SER_RS485_ENABLED) {
|
|
|
|
efcr |= SC16IS7XX_EFCR_AUTO_RS485_BIT;
|
|
|
|
|
|
|
|
if (rs485->flags & SER_RS485_RTS_AFTER_SEND)
|
|
|
|
efcr |= SC16IS7XX_EFCR_RTS_INVERT_BIT;
|
|
|
|
}
|
|
|
|
spin_unlock_irqrestore(&port->lock, irqflags);
|
|
|
|
|
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG, mask, efcr);
|
|
|
|
}
|
|
|
|
|
2015-05-30 02:20:31 +07:00
|
|
|
static void sc16is7xx_reg_proc(struct kthread_work *ws)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_one *one = to_sc16is7xx_one(ws, reg_work);
|
|
|
|
struct sc16is7xx_one_config config;
|
|
|
|
unsigned long irqflags;
|
|
|
|
|
|
|
|
spin_lock_irqsave(&one->port.lock, irqflags);
|
|
|
|
config = one->config;
|
|
|
|
memset(&one->config, 0, sizeof(one->config));
|
|
|
|
spin_unlock_irqrestore(&one->port.lock, irqflags);
|
|
|
|
|
2016-02-14 21:05:25 +07:00
|
|
|
if (config.flags & SC16IS7XX_RECONF_MD) {
|
2015-05-30 02:20:31 +07:00
|
|
|
sc16is7xx_port_update(&one->port, SC16IS7XX_MCR_REG,
|
|
|
|
SC16IS7XX_MCR_LOOP_BIT,
|
|
|
|
(one->port.mctrl & TIOCM_LOOP) ?
|
|
|
|
SC16IS7XX_MCR_LOOP_BIT : 0);
|
2016-02-14 21:05:25 +07:00
|
|
|
sc16is7xx_port_update(&one->port, SC16IS7XX_MCR_REG,
|
|
|
|
SC16IS7XX_MCR_RTS_BIT,
|
|
|
|
(one->port.mctrl & TIOCM_RTS) ?
|
|
|
|
SC16IS7XX_MCR_RTS_BIT : 0);
|
|
|
|
sc16is7xx_port_update(&one->port, SC16IS7XX_MCR_REG,
|
|
|
|
SC16IS7XX_MCR_DTR_BIT,
|
|
|
|
(one->port.mctrl & TIOCM_DTR) ?
|
|
|
|
SC16IS7XX_MCR_DTR_BIT : 0);
|
|
|
|
}
|
2015-05-30 02:20:32 +07:00
|
|
|
if (config.flags & SC16IS7XX_RECONF_IER)
|
|
|
|
sc16is7xx_port_update(&one->port, SC16IS7XX_IER_REG,
|
|
|
|
config.ier_clear, 0);
|
2015-05-30 02:20:33 +07:00
|
|
|
|
|
|
|
if (config.flags & SC16IS7XX_RECONF_RS485)
|
|
|
|
sc16is7xx_reconf_rs485(&one->port);
|
2015-05-30 02:20:31 +07:00
|
|
|
}
|
|
|
|
|
2015-05-30 02:20:32 +07:00
|
|
|
static void sc16is7xx_ier_clear(struct uart_port *port, u8 bit)
|
2014-04-25 07:56:06 +07:00
|
|
|
{
|
2015-05-30 02:20:32 +07:00
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
|
|
|
|
|
|
|
|
one->config.flags |= SC16IS7XX_RECONF_IER;
|
|
|
|
one->config.ier_clear |= bit;
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_queue_work(&s->kworker, &one->reg_work);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
2015-05-30 02:20:32 +07:00
|
|
|
static void sc16is7xx_stop_tx(struct uart_port *port)
|
2014-04-25 07:56:06 +07:00
|
|
|
{
|
2015-05-30 02:20:32 +07:00
|
|
|
sc16is7xx_ier_clear(port, SC16IS7XX_IER_THRI_BIT);
|
|
|
|
}
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2015-05-30 02:20:32 +07:00
|
|
|
static void sc16is7xx_stop_rx(struct uart_port *port)
|
|
|
|
{
|
|
|
|
sc16is7xx_ier_clear(port, SC16IS7XX_IER_RDI_BIT);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_start_tx(struct uart_port *port)
|
|
|
|
{
|
2015-05-30 02:20:29 +07:00
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
2014-04-25 07:56:06 +07:00
|
|
|
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
|
|
|
|
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_queue_work(&s->kworker, &one->tx_work);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int sc16is7xx_tx_empty(struct uart_port *port)
|
|
|
|
{
|
2015-05-30 02:20:28 +07:00
|
|
|
unsigned int lsr;
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
lsr = sc16is7xx_port_read(port, SC16IS7XX_LSR_REG);
|
|
|
|
|
2015-05-30 02:20:28 +07:00
|
|
|
return (lsr & SC16IS7XX_LSR_TEMT_BIT) ? TIOCSER_TEMT : 0;
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int sc16is7xx_get_mctrl(struct uart_port *port)
|
|
|
|
{
|
2016-03-08 07:10:08 +07:00
|
|
|
/* DCD and DSR are not wired and CTS/RTS is handled automatically
|
|
|
|
* so just indicate DSR and CAR asserted
|
|
|
|
*/
|
|
|
|
return TIOCM_DSR | TIOCM_CAR;
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_set_mctrl(struct uart_port *port, unsigned int mctrl)
|
|
|
|
{
|
2015-05-30 02:20:31 +07:00
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
2014-04-25 07:56:06 +07:00
|
|
|
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
|
|
|
|
|
2015-05-30 02:20:31 +07:00
|
|
|
one->config.flags |= SC16IS7XX_RECONF_MD;
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_queue_work(&s->kworker, &one->reg_work);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_break_ctl(struct uart_port *port, int break_state)
|
|
|
|
{
|
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_LCR_REG,
|
|
|
|
SC16IS7XX_LCR_TXBREAK_BIT,
|
|
|
|
break_state ? SC16IS7XX_LCR_TXBREAK_BIT : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_set_termios(struct uart_port *port,
|
|
|
|
struct ktermios *termios,
|
|
|
|
struct ktermios *old)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
unsigned int lcr, flow = 0;
|
|
|
|
int baud;
|
|
|
|
|
|
|
|
/* Mask termios capabilities we don't support */
|
|
|
|
termios->c_cflag &= ~CMSPAR;
|
|
|
|
|
|
|
|
/* Word size */
|
|
|
|
switch (termios->c_cflag & CSIZE) {
|
|
|
|
case CS5:
|
|
|
|
lcr = SC16IS7XX_LCR_WORD_LEN_5;
|
|
|
|
break;
|
|
|
|
case CS6:
|
|
|
|
lcr = SC16IS7XX_LCR_WORD_LEN_6;
|
|
|
|
break;
|
|
|
|
case CS7:
|
|
|
|
lcr = SC16IS7XX_LCR_WORD_LEN_7;
|
|
|
|
break;
|
|
|
|
case CS8:
|
|
|
|
lcr = SC16IS7XX_LCR_WORD_LEN_8;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
lcr = SC16IS7XX_LCR_WORD_LEN_8;
|
|
|
|
termios->c_cflag &= ~CSIZE;
|
|
|
|
termios->c_cflag |= CS8;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Parity */
|
|
|
|
if (termios->c_cflag & PARENB) {
|
|
|
|
lcr |= SC16IS7XX_LCR_PARITY_BIT;
|
|
|
|
if (!(termios->c_cflag & PARODD))
|
|
|
|
lcr |= SC16IS7XX_LCR_EVENPARITY_BIT;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stop bits */
|
|
|
|
if (termios->c_cflag & CSTOPB)
|
|
|
|
lcr |= SC16IS7XX_LCR_STOPLEN_BIT; /* 2 stops */
|
|
|
|
|
|
|
|
/* Set read status mask */
|
|
|
|
port->read_status_mask = SC16IS7XX_LSR_OE_BIT;
|
|
|
|
if (termios->c_iflag & INPCK)
|
|
|
|
port->read_status_mask |= SC16IS7XX_LSR_PE_BIT |
|
|
|
|
SC16IS7XX_LSR_FE_BIT;
|
|
|
|
if (termios->c_iflag & (BRKINT | PARMRK))
|
|
|
|
port->read_status_mask |= SC16IS7XX_LSR_BI_BIT;
|
|
|
|
|
|
|
|
/* Set status ignore mask */
|
|
|
|
port->ignore_status_mask = 0;
|
|
|
|
if (termios->c_iflag & IGNBRK)
|
|
|
|
port->ignore_status_mask |= SC16IS7XX_LSR_BI_BIT;
|
|
|
|
if (!(termios->c_cflag & CREAD))
|
|
|
|
port->ignore_status_mask |= SC16IS7XX_LSR_BRK_ERROR_MASK;
|
|
|
|
|
2018-09-12 21:31:56 +07:00
|
|
|
/* As above, claim the mutex while accessing the EFR. */
|
|
|
|
mutex_lock(&s->efr_lock);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
|
|
|
|
SC16IS7XX_LCR_CONF_MODE_B);
|
|
|
|
|
|
|
|
/* Configure flow control */
|
|
|
|
regcache_cache_bypass(s->regmap, true);
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_XON1_REG, termios->c_cc[VSTART]);
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_XOFF1_REG, termios->c_cc[VSTOP]);
|
|
|
|
if (termios->c_cflag & CRTSCTS)
|
|
|
|
flow |= SC16IS7XX_EFR_AUTOCTS_BIT |
|
|
|
|
SC16IS7XX_EFR_AUTORTS_BIT;
|
|
|
|
if (termios->c_iflag & IXON)
|
|
|
|
flow |= SC16IS7XX_EFR_SWFLOW3_BIT;
|
|
|
|
if (termios->c_iflag & IXOFF)
|
|
|
|
flow |= SC16IS7XX_EFR_SWFLOW1_BIT;
|
|
|
|
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_EFR_REG, flow);
|
|
|
|
regcache_cache_bypass(s->regmap, false);
|
|
|
|
|
|
|
|
/* Update LCR register */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, lcr);
|
|
|
|
|
2018-09-12 21:31:56 +07:00
|
|
|
mutex_unlock(&s->efr_lock);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
/* Get baud rate generator configuration */
|
|
|
|
baud = uart_get_baud_rate(port, termios, old,
|
|
|
|
port->uartclk / 16 / 4 / 0xffff,
|
|
|
|
port->uartclk / 16);
|
|
|
|
|
|
|
|
/* Setup baudrate generator */
|
|
|
|
baud = sc16is7xx_set_baud(port, baud);
|
|
|
|
|
|
|
|
/* Update timeout according to new baud rate */
|
|
|
|
uart_update_timeout(port, termios->c_cflag, baud);
|
|
|
|
}
|
|
|
|
|
2014-11-06 15:22:54 +07:00
|
|
|
static int sc16is7xx_config_rs485(struct uart_port *port,
|
2015-04-01 02:11:40 +07:00
|
|
|
struct serial_rs485 *rs485)
|
2014-04-25 07:56:06 +07:00
|
|
|
{
|
2015-05-30 02:20:33 +07:00
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
struct sc16is7xx_one *one = to_sc16is7xx_one(port, port);
|
2015-04-01 02:11:40 +07:00
|
|
|
|
|
|
|
if (rs485->flags & SER_RS485_ENABLED) {
|
|
|
|
bool rts_during_rx, rts_during_tx;
|
|
|
|
|
|
|
|
rts_during_rx = rs485->flags & SER_RS485_RTS_AFTER_SEND;
|
|
|
|
rts_during_tx = rs485->flags & SER_RS485_RTS_ON_SEND;
|
|
|
|
|
2015-05-30 02:20:33 +07:00
|
|
|
if (rts_during_rx == rts_during_tx)
|
2015-04-01 02:11:40 +07:00
|
|
|
dev_err(port->dev,
|
|
|
|
"unsupported RTS signalling on_send:%d after_send:%d - exactly one of RS485 RTS flags should be set\n",
|
|
|
|
rts_during_tx, rts_during_rx);
|
2015-05-30 02:20:26 +07:00
|
|
|
|
|
|
|
/*
|
|
|
|
* RTS signal is handled by HW, it's timing can't be influenced.
|
|
|
|
* However, it's sometimes useful to delay TX even without RTS
|
|
|
|
* control therefore we try to handle .delay_rts_before_send.
|
|
|
|
*/
|
|
|
|
if (rs485->delay_rts_after_send)
|
|
|
|
return -EINVAL;
|
2015-04-01 02:11:40 +07:00
|
|
|
}
|
|
|
|
|
2014-11-06 15:22:54 +07:00
|
|
|
port->rs485 = *rs485;
|
2015-05-30 02:20:33 +07:00
|
|
|
one->config.flags |= SC16IS7XX_RECONF_RS485;
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_queue_work(&s->kworker, &one->reg_work);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2014-11-06 15:22:54 +07:00
|
|
|
return 0;
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_startup(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
unsigned int val;
|
|
|
|
|
|
|
|
sc16is7xx_power(port, 1);
|
|
|
|
|
|
|
|
/* Reset FIFOs*/
|
|
|
|
val = SC16IS7XX_FCR_RXRESET_BIT | SC16IS7XX_FCR_TXRESET_BIT;
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_FCR_REG, val);
|
|
|
|
udelay(5);
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_FCR_REG,
|
|
|
|
SC16IS7XX_FCR_FIFO_BIT);
|
|
|
|
|
|
|
|
/* Enable EFR */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG,
|
|
|
|
SC16IS7XX_LCR_CONF_MODE_B);
|
|
|
|
|
|
|
|
regcache_cache_bypass(s->regmap, true);
|
|
|
|
|
|
|
|
/* Enable write access to enhanced features and internal clock div */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_EFR_REG,
|
|
|
|
SC16IS7XX_EFR_ENABLE_BIT);
|
|
|
|
|
|
|
|
/* Enable TCR/TLR */
|
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_MCR_REG,
|
|
|
|
SC16IS7XX_MCR_TCRTLR_BIT,
|
|
|
|
SC16IS7XX_MCR_TCRTLR_BIT);
|
|
|
|
|
|
|
|
/* Configure flow control levels */
|
|
|
|
/* Flow control halt level 48, resume level 24 */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_TCR_REG,
|
|
|
|
SC16IS7XX_TCR_RX_RESUME(24) |
|
|
|
|
SC16IS7XX_TCR_RX_HALT(48));
|
|
|
|
|
|
|
|
regcache_cache_bypass(s->regmap, false);
|
|
|
|
|
|
|
|
/* Now, initialize the UART */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_LCR_REG, SC16IS7XX_LCR_WORD_LEN_8);
|
|
|
|
|
|
|
|
/* Enable the Rx and Tx FIFO */
|
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG,
|
|
|
|
SC16IS7XX_EFCR_RXDISABLE_BIT |
|
|
|
|
SC16IS7XX_EFCR_TXDISABLE_BIT,
|
|
|
|
0);
|
|
|
|
|
2016-03-05 21:09:05 +07:00
|
|
|
/* Enable RX, TX interrupts */
|
|
|
|
val = SC16IS7XX_IER_RDI_BIT | SC16IS7XX_IER_THRI_BIT;
|
2014-04-25 07:56:06 +07:00
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_IER_REG, val);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_shutdown(struct uart_port *port)
|
|
|
|
{
|
2015-05-30 02:20:29 +07:00
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
/* Disable all interrupts */
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_IER_REG, 0);
|
|
|
|
/* Disable TX/RX */
|
2015-03-17 06:28:47 +07:00
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_EFCR_REG,
|
|
|
|
SC16IS7XX_EFCR_RXDISABLE_BIT |
|
|
|
|
SC16IS7XX_EFCR_TXDISABLE_BIT,
|
|
|
|
SC16IS7XX_EFCR_RXDISABLE_BIT |
|
|
|
|
SC16IS7XX_EFCR_TXDISABLE_BIT);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
sc16is7xx_power(port, 0);
|
2015-05-30 02:20:29 +07:00
|
|
|
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_flush_worker(&s->kworker);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *sc16is7xx_type(struct uart_port *port)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(port->dev);
|
|
|
|
|
|
|
|
return (port->type == PORT_SC16IS7XX) ? s->devtype->name : NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_request_port(struct uart_port *port)
|
|
|
|
{
|
|
|
|
/* Do nothing */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_config_port(struct uart_port *port, int flags)
|
|
|
|
{
|
|
|
|
if (flags & UART_CONFIG_TYPE)
|
|
|
|
port->type = PORT_SC16IS7XX;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_verify_port(struct uart_port *port,
|
|
|
|
struct serial_struct *s)
|
|
|
|
{
|
|
|
|
if ((s->type != PORT_UNKNOWN) && (s->type != PORT_SC16IS7XX))
|
|
|
|
return -EINVAL;
|
|
|
|
if (s->irq != port->irq)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_pm(struct uart_port *port, unsigned int state,
|
|
|
|
unsigned int oldstate)
|
|
|
|
{
|
|
|
|
sc16is7xx_power(port, (state == UART_PM_STATE_ON) ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_null_void(struct uart_port *port)
|
|
|
|
{
|
|
|
|
/* Do nothing */
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct uart_ops sc16is7xx_ops = {
|
|
|
|
.tx_empty = sc16is7xx_tx_empty,
|
|
|
|
.set_mctrl = sc16is7xx_set_mctrl,
|
|
|
|
.get_mctrl = sc16is7xx_get_mctrl,
|
|
|
|
.stop_tx = sc16is7xx_stop_tx,
|
|
|
|
.start_tx = sc16is7xx_start_tx,
|
|
|
|
.stop_rx = sc16is7xx_stop_rx,
|
|
|
|
.break_ctl = sc16is7xx_break_ctl,
|
|
|
|
.startup = sc16is7xx_startup,
|
|
|
|
.shutdown = sc16is7xx_shutdown,
|
|
|
|
.set_termios = sc16is7xx_set_termios,
|
|
|
|
.type = sc16is7xx_type,
|
|
|
|
.request_port = sc16is7xx_request_port,
|
|
|
|
.release_port = sc16is7xx_null_void,
|
|
|
|
.config_port = sc16is7xx_config_port,
|
|
|
|
.verify_port = sc16is7xx_verify_port,
|
|
|
|
.pm = sc16is7xx_pm,
|
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_GPIOLIB
|
|
|
|
static int sc16is7xx_gpio_get(struct gpio_chip *chip, unsigned offset)
|
|
|
|
{
|
|
|
|
unsigned int val;
|
2015-12-09 05:15:53 +07:00
|
|
|
struct sc16is7xx_port *s = gpiochip_get_data(chip);
|
2014-04-25 07:56:06 +07:00
|
|
|
struct uart_port *port = &s->p[0].port;
|
|
|
|
|
|
|
|
val = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG);
|
|
|
|
|
|
|
|
return !!(val & BIT(offset));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sc16is7xx_gpio_set(struct gpio_chip *chip, unsigned offset, int val)
|
|
|
|
{
|
2015-12-09 05:15:53 +07:00
|
|
|
struct sc16is7xx_port *s = gpiochip_get_data(chip);
|
2014-04-25 07:56:06 +07:00
|
|
|
struct uart_port *port = &s->p[0].port;
|
|
|
|
|
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_IOSTATE_REG, BIT(offset),
|
|
|
|
val ? BIT(offset) : 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_gpio_direction_input(struct gpio_chip *chip,
|
|
|
|
unsigned offset)
|
|
|
|
{
|
2015-12-09 05:15:53 +07:00
|
|
|
struct sc16is7xx_port *s = gpiochip_get_data(chip);
|
2014-04-25 07:56:06 +07:00
|
|
|
struct uart_port *port = &s->p[0].port;
|
|
|
|
|
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_IODIR_REG, BIT(offset), 0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_gpio_direction_output(struct gpio_chip *chip,
|
|
|
|
unsigned offset, int val)
|
|
|
|
{
|
2015-12-09 05:15:53 +07:00
|
|
|
struct sc16is7xx_port *s = gpiochip_get_data(chip);
|
2014-04-25 07:56:06 +07:00
|
|
|
struct uart_port *port = &s->p[0].port;
|
2016-10-25 19:24:13 +07:00
|
|
|
u8 state = sc16is7xx_port_read(port, SC16IS7XX_IOSTATE_REG);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2016-10-25 19:24:13 +07:00
|
|
|
if (val)
|
|
|
|
state |= BIT(offset);
|
|
|
|
else
|
|
|
|
state &= ~BIT(offset);
|
|
|
|
sc16is7xx_port_write(port, SC16IS7XX_IOSTATE_REG, state);
|
2014-04-25 07:56:06 +07:00
|
|
|
sc16is7xx_port_update(port, SC16IS7XX_IODIR_REG, BIT(offset),
|
|
|
|
BIT(offset));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int sc16is7xx_probe(struct device *dev,
|
2015-07-31 19:44:24 +07:00
|
|
|
const struct sc16is7xx_devtype *devtype,
|
2014-04-25 07:56:06 +07:00
|
|
|
struct regmap *regmap, int irq, unsigned long flags)
|
|
|
|
{
|
2015-05-30 02:20:29 +07:00
|
|
|
struct sched_param sched_param = { .sched_priority = MAX_RT_PRIO / 2 };
|
2014-04-25 07:56:06 +07:00
|
|
|
unsigned long freq, *pfreq = dev_get_platdata(dev);
|
|
|
|
int i, ret;
|
|
|
|
struct sc16is7xx_port *s;
|
|
|
|
|
|
|
|
if (IS_ERR(regmap))
|
|
|
|
return PTR_ERR(regmap);
|
|
|
|
|
|
|
|
/* Alloc port structure */
|
2019-01-05 00:48:19 +07:00
|
|
|
s = devm_kzalloc(dev, struct_size(s, p, devtype->nr_uart), GFP_KERNEL);
|
2014-04-25 07:56:06 +07:00
|
|
|
if (!s) {
|
|
|
|
dev_err(dev, "Error allocating port structure\n");
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2014-07-26 00:53:36 +07:00
|
|
|
s->clk = devm_clk_get(dev, NULL);
|
|
|
|
if (IS_ERR(s->clk)) {
|
2014-04-25 07:56:06 +07:00
|
|
|
if (pfreq)
|
|
|
|
freq = *pfreq;
|
|
|
|
else
|
2014-07-26 00:53:36 +07:00
|
|
|
return PTR_ERR(s->clk);
|
2014-04-25 07:56:06 +07:00
|
|
|
} else {
|
2018-04-24 23:03:47 +07:00
|
|
|
ret = clk_prepare_enable(s->clk);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2014-07-26 00:53:36 +07:00
|
|
|
freq = clk_get_rate(s->clk);
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
s->regmap = regmap;
|
|
|
|
s->devtype = devtype;
|
|
|
|
dev_set_drvdata(dev, s);
|
2018-09-12 21:31:56 +07:00
|
|
|
mutex_init(&s->efr_lock);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_init_worker(&s->kworker);
|
|
|
|
kthread_init_work(&s->irq_work, sc16is7xx_ist);
|
2015-05-30 02:20:29 +07:00
|
|
|
s->kworker_task = kthread_run(kthread_worker_fn, &s->kworker,
|
|
|
|
"sc16is7xx");
|
|
|
|
if (IS_ERR(s->kworker_task)) {
|
|
|
|
ret = PTR_ERR(s->kworker_task);
|
2015-07-31 19:44:23 +07:00
|
|
|
goto out_clk;
|
2015-05-30 02:20:29 +07:00
|
|
|
}
|
|
|
|
sched_setscheduler(s->kworker_task, SCHED_FIFO, &sched_param);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
#ifdef CONFIG_GPIOLIB
|
|
|
|
if (devtype->nr_gpio) {
|
|
|
|
/* Setup GPIO cotroller */
|
|
|
|
s->gpio.owner = THIS_MODULE;
|
2015-11-04 15:56:26 +07:00
|
|
|
s->gpio.parent = dev;
|
2014-04-25 07:56:06 +07:00
|
|
|
s->gpio.label = dev_name(dev);
|
|
|
|
s->gpio.direction_input = sc16is7xx_gpio_direction_input;
|
|
|
|
s->gpio.get = sc16is7xx_gpio_get;
|
|
|
|
s->gpio.direction_output = sc16is7xx_gpio_direction_output;
|
|
|
|
s->gpio.set = sc16is7xx_gpio_set;
|
|
|
|
s->gpio.base = -1;
|
|
|
|
s->gpio.ngpio = devtype->nr_gpio;
|
|
|
|
s->gpio.can_sleep = 1;
|
2015-12-09 05:15:53 +07:00
|
|
|
ret = gpiochip_add_data(&s->gpio, s);
|
2014-04-25 07:56:06 +07:00
|
|
|
if (ret)
|
2015-05-30 02:20:29 +07:00
|
|
|
goto out_thread;
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-07-19 21:29:36 +07:00
|
|
|
/* reset device, purging any pending irq / data */
|
|
|
|
regmap_write(s->regmap, SC16IS7XX_IOCONTROL_REG << SC16IS7XX_REG_SHIFT,
|
|
|
|
SC16IS7XX_IOCONTROL_SRESET_BIT);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
for (i = 0; i < devtype->nr_uart; ++i) {
|
2015-07-31 19:44:22 +07:00
|
|
|
s->p[i].line = i;
|
2014-04-25 07:56:06 +07:00
|
|
|
/* Initialize port data */
|
|
|
|
s->p[i].port.dev = dev;
|
|
|
|
s->p[i].port.irq = irq;
|
|
|
|
s->p[i].port.type = PORT_SC16IS7XX;
|
|
|
|
s->p[i].port.fifosize = SC16IS7XX_FIFO_SIZE;
|
|
|
|
s->p[i].port.flags = UPF_FIXED_TYPE | UPF_LOW_LATENCY;
|
|
|
|
s->p[i].port.iotype = UPIO_PORT;
|
|
|
|
s->p[i].port.uartclk = freq;
|
2014-11-06 15:22:54 +07:00
|
|
|
s->p[i].port.rs485_config = sc16is7xx_config_rs485;
|
2014-04-25 07:56:06 +07:00
|
|
|
s->p[i].port.ops = &sc16is7xx_ops;
|
2015-07-31 19:44:23 +07:00
|
|
|
s->p[i].port.line = sc16is7xx_alloc_line();
|
|
|
|
if (s->p[i].port.line >= SC16IS7XX_MAX_DEVS) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out_ports;
|
|
|
|
}
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
/* Disable all interrupts */
|
|
|
|
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_IER_REG, 0);
|
|
|
|
/* Disable TX/RX */
|
|
|
|
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFCR_REG,
|
|
|
|
SC16IS7XX_EFCR_RXDISABLE_BIT |
|
|
|
|
SC16IS7XX_EFCR_TXDISABLE_BIT);
|
2015-05-30 02:20:31 +07:00
|
|
|
/* Initialize kthread work structs */
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_init_work(&s->p[i].tx_work, sc16is7xx_tx_proc);
|
|
|
|
kthread_init_work(&s->p[i].reg_work, sc16is7xx_reg_proc);
|
2014-04-25 07:56:06 +07:00
|
|
|
/* Register port */
|
2015-07-31 19:44:23 +07:00
|
|
|
uart_add_one_port(&sc16is7xx_uart, &s->p[i].port);
|
2016-07-19 21:29:36 +07:00
|
|
|
|
|
|
|
/* Enable EFR */
|
|
|
|
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG,
|
|
|
|
SC16IS7XX_LCR_CONF_MODE_B);
|
|
|
|
|
|
|
|
regcache_cache_bypass(s->regmap, true);
|
|
|
|
|
|
|
|
/* Enable write access to enhanced features */
|
|
|
|
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_EFR_REG,
|
|
|
|
SC16IS7XX_EFR_ENABLE_BIT);
|
|
|
|
|
|
|
|
regcache_cache_bypass(s->regmap, false);
|
|
|
|
|
|
|
|
/* Restore access to general registers */
|
|
|
|
sc16is7xx_port_write(&s->p[i].port, SC16IS7XX_LCR_REG, 0x00);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
/* Go to suspend mode */
|
|
|
|
sc16is7xx_power(&s->p[i].port, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Setup interrupt */
|
2015-05-30 02:20:29 +07:00
|
|
|
ret = devm_request_irq(dev, irq, sc16is7xx_irq,
|
2016-10-13 22:44:33 +07:00
|
|
|
flags, dev_name(dev), s);
|
2014-04-25 07:56:06 +07:00
|
|
|
if (!ret)
|
|
|
|
return 0;
|
|
|
|
|
2015-07-31 19:44:23 +07:00
|
|
|
out_ports:
|
|
|
|
for (i--; i >= 0; i--) {
|
|
|
|
uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port);
|
|
|
|
clear_bit(s->p[i].port.line, &sc16is7xx_lines);
|
|
|
|
}
|
2015-03-17 06:28:46 +07:00
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
#ifdef CONFIG_GPIOLIB
|
|
|
|
if (devtype->nr_gpio)
|
2014-09-17 05:14:46 +07:00
|
|
|
gpiochip_remove(&s->gpio);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
2015-05-30 02:20:29 +07:00
|
|
|
out_thread:
|
2014-04-25 07:56:06 +07:00
|
|
|
#endif
|
2015-05-30 02:20:29 +07:00
|
|
|
kthread_stop(s->kworker_task);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
out_clk:
|
|
|
|
if (!IS_ERR(s->clk))
|
|
|
|
clk_disable_unprepare(s->clk);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_remove(struct device *dev)
|
|
|
|
{
|
|
|
|
struct sc16is7xx_port *s = dev_get_drvdata(dev);
|
2014-09-17 05:14:46 +07:00
|
|
|
int i;
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
#ifdef CONFIG_GPIOLIB
|
2014-09-17 05:14:46 +07:00
|
|
|
if (s->devtype->nr_gpio)
|
|
|
|
gpiochip_remove(&s->gpio);
|
2014-04-25 07:56:06 +07:00
|
|
|
#endif
|
|
|
|
|
2015-07-31 19:44:23 +07:00
|
|
|
for (i = 0; i < s->devtype->nr_uart; i++) {
|
|
|
|
uart_remove_one_port(&sc16is7xx_uart, &s->p[i].port);
|
|
|
|
clear_bit(s->p[i].port.line, &sc16is7xx_lines);
|
2014-04-25 07:56:06 +07:00
|
|
|
sc16is7xx_power(&s->p[i].port, 0);
|
|
|
|
}
|
|
|
|
|
2016-10-12 03:55:20 +07:00
|
|
|
kthread_flush_worker(&s->kworker);
|
2015-05-30 02:20:29 +07:00
|
|
|
kthread_stop(s->kworker_task);
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
if (!IS_ERR(s->clk))
|
|
|
|
clk_disable_unprepare(s->clk);
|
|
|
|
|
2014-09-17 05:14:46 +07:00
|
|
|
return 0;
|
2014-04-25 07:56:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct of_device_id __maybe_unused sc16is7xx_dt_ids[] = {
|
|
|
|
{ .compatible = "nxp,sc16is740", .data = &sc16is74x_devtype, },
|
|
|
|
{ .compatible = "nxp,sc16is741", .data = &sc16is74x_devtype, },
|
|
|
|
{ .compatible = "nxp,sc16is750", .data = &sc16is750_devtype, },
|
|
|
|
{ .compatible = "nxp,sc16is752", .data = &sc16is752_devtype, },
|
|
|
|
{ .compatible = "nxp,sc16is760", .data = &sc16is760_devtype, },
|
|
|
|
{ .compatible = "nxp,sc16is762", .data = &sc16is762_devtype, },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, sc16is7xx_dt_ids);
|
|
|
|
|
|
|
|
static struct regmap_config regcfg = {
|
|
|
|
.reg_bits = 7,
|
|
|
|
.pad_bits = 1,
|
|
|
|
.val_bits = 8,
|
|
|
|
.cache_type = REGCACHE_RBTREE,
|
|
|
|
.volatile_reg = sc16is7xx_regmap_volatile,
|
|
|
|
.precious_reg = sc16is7xx_regmap_precious,
|
|
|
|
};
|
|
|
|
|
2015-05-25 13:21:09 +07:00
|
|
|
#ifdef CONFIG_SERIAL_SC16IS7XX_SPI
|
|
|
|
static int sc16is7xx_spi_probe(struct spi_device *spi)
|
|
|
|
{
|
2015-07-31 19:44:24 +07:00
|
|
|
const struct sc16is7xx_devtype *devtype;
|
2015-05-25 13:21:09 +07:00
|
|
|
unsigned long flags = 0;
|
|
|
|
struct regmap *regmap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Setup SPI bus */
|
|
|
|
spi->bits_per_word = 8;
|
|
|
|
/* only supports mode 0 on SC16IS762 */
|
|
|
|
spi->mode = spi->mode ? : SPI_MODE_0;
|
|
|
|
spi->max_speed_hz = spi->max_speed_hz ? : 15000000;
|
|
|
|
ret = spi_setup(spi);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
if (spi->dev.of_node) {
|
|
|
|
const struct of_device_id *of_id =
|
|
|
|
of_match_device(sc16is7xx_dt_ids, &spi->dev);
|
|
|
|
|
2015-10-04 23:59:45 +07:00
|
|
|
if (!of_id)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2015-05-25 13:21:09 +07:00
|
|
|
devtype = (struct sc16is7xx_devtype *)of_id->data;
|
|
|
|
} else {
|
|
|
|
const struct spi_device_id *id_entry = spi_get_device_id(spi);
|
|
|
|
|
|
|
|
devtype = (struct sc16is7xx_devtype *)id_entry->driver_data;
|
|
|
|
flags = IRQF_TRIGGER_FALLING;
|
|
|
|
}
|
|
|
|
|
|
|
|
regcfg.max_register = (0xf << SC16IS7XX_REG_SHIFT) |
|
|
|
|
(devtype->nr_uart - 1);
|
|
|
|
regmap = devm_regmap_init_spi(spi, ®cfg);
|
|
|
|
|
|
|
|
return sc16is7xx_probe(&spi->dev, devtype, regmap, spi->irq, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_spi_remove(struct spi_device *spi)
|
|
|
|
{
|
|
|
|
return sc16is7xx_remove(&spi->dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct spi_device_id sc16is7xx_spi_id_table[] = {
|
|
|
|
{ "sc16is74x", (kernel_ulong_t)&sc16is74x_devtype, },
|
2015-05-30 02:20:27 +07:00
|
|
|
{ "sc16is740", (kernel_ulong_t)&sc16is74x_devtype, },
|
|
|
|
{ "sc16is741", (kernel_ulong_t)&sc16is74x_devtype, },
|
2015-05-25 13:21:09 +07:00
|
|
|
{ "sc16is750", (kernel_ulong_t)&sc16is750_devtype, },
|
|
|
|
{ "sc16is752", (kernel_ulong_t)&sc16is752_devtype, },
|
|
|
|
{ "sc16is760", (kernel_ulong_t)&sc16is760_devtype, },
|
|
|
|
{ "sc16is762", (kernel_ulong_t)&sc16is762_devtype, },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_DEVICE_TABLE(spi, sc16is7xx_spi_id_table);
|
|
|
|
|
|
|
|
static struct spi_driver sc16is7xx_spi_uart_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = SC16IS7XX_NAME,
|
|
|
|
.of_match_table = of_match_ptr(sc16is7xx_dt_ids),
|
|
|
|
},
|
|
|
|
.probe = sc16is7xx_spi_probe,
|
|
|
|
.remove = sc16is7xx_spi_remove,
|
|
|
|
.id_table = sc16is7xx_spi_id_table,
|
|
|
|
};
|
|
|
|
|
|
|
|
MODULE_ALIAS("spi:sc16is7xx");
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_SERIAL_SC16IS7XX_I2C
|
2014-04-25 07:56:06 +07:00
|
|
|
static int sc16is7xx_i2c_probe(struct i2c_client *i2c,
|
|
|
|
const struct i2c_device_id *id)
|
|
|
|
{
|
2015-07-31 19:44:24 +07:00
|
|
|
const struct sc16is7xx_devtype *devtype;
|
2014-04-25 07:56:06 +07:00
|
|
|
unsigned long flags = 0;
|
|
|
|
struct regmap *regmap;
|
|
|
|
|
|
|
|
if (i2c->dev.of_node) {
|
|
|
|
const struct of_device_id *of_id =
|
|
|
|
of_match_device(sc16is7xx_dt_ids, &i2c->dev);
|
|
|
|
|
2015-10-04 23:59:45 +07:00
|
|
|
if (!of_id)
|
|
|
|
return -ENODEV;
|
|
|
|
|
2014-04-25 07:56:06 +07:00
|
|
|
devtype = (struct sc16is7xx_devtype *)of_id->data;
|
|
|
|
} else {
|
|
|
|
devtype = (struct sc16is7xx_devtype *)id->driver_data;
|
|
|
|
flags = IRQF_TRIGGER_FALLING;
|
|
|
|
}
|
|
|
|
|
|
|
|
regcfg.max_register = (0xf << SC16IS7XX_REG_SHIFT) |
|
|
|
|
(devtype->nr_uart - 1);
|
|
|
|
regmap = devm_regmap_init_i2c(i2c, ®cfg);
|
|
|
|
|
|
|
|
return sc16is7xx_probe(&i2c->dev, devtype, regmap, i2c->irq, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sc16is7xx_i2c_remove(struct i2c_client *client)
|
|
|
|
{
|
|
|
|
return sc16is7xx_remove(&client->dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct i2c_device_id sc16is7xx_i2c_id_table[] = {
|
|
|
|
{ "sc16is74x", (kernel_ulong_t)&sc16is74x_devtype, },
|
2015-05-30 02:20:27 +07:00
|
|
|
{ "sc16is740", (kernel_ulong_t)&sc16is74x_devtype, },
|
|
|
|
{ "sc16is741", (kernel_ulong_t)&sc16is74x_devtype, },
|
2014-04-25 07:56:06 +07:00
|
|
|
{ "sc16is750", (kernel_ulong_t)&sc16is750_devtype, },
|
|
|
|
{ "sc16is752", (kernel_ulong_t)&sc16is752_devtype, },
|
|
|
|
{ "sc16is760", (kernel_ulong_t)&sc16is760_devtype, },
|
|
|
|
{ "sc16is762", (kernel_ulong_t)&sc16is762_devtype, },
|
|
|
|
{ }
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(i2c, sc16is7xx_i2c_id_table);
|
|
|
|
|
|
|
|
static struct i2c_driver sc16is7xx_i2c_uart_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = SC16IS7XX_NAME,
|
|
|
|
.of_match_table = of_match_ptr(sc16is7xx_dt_ids),
|
|
|
|
},
|
|
|
|
.probe = sc16is7xx_i2c_probe,
|
|
|
|
.remove = sc16is7xx_i2c_remove,
|
|
|
|
.id_table = sc16is7xx_i2c_id_table,
|
|
|
|
};
|
2015-05-25 13:21:09 +07:00
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int __init sc16is7xx_init(void)
|
|
|
|
{
|
2015-07-31 19:44:23 +07:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = uart_register_driver(&sc16is7xx_uart);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("Registering UART driver failed\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-05-25 13:21:09 +07:00
|
|
|
#ifdef CONFIG_SERIAL_SC16IS7XX_I2C
|
|
|
|
ret = i2c_add_driver(&sc16is7xx_i2c_uart_driver);
|
|
|
|
if (ret < 0) {
|
|
|
|
pr_err("failed to init sc16is7xx i2c --> %d\n", ret);
|
2019-03-08 21:08:31 +07:00
|
|
|
goto err_i2c;
|
2015-05-25 13:21:09 +07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_SERIAL_SC16IS7XX_SPI
|
|
|
|
ret = spi_register_driver(&sc16is7xx_spi_uart_driver);
|
|
|
|
if (ret < 0) {
|
|
|
|
pr_err("failed to init sc16is7xx spi --> %d\n", ret);
|
2019-03-08 21:08:31 +07:00
|
|
|
goto err_spi;
|
2015-05-25 13:21:09 +07:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return ret;
|
2019-03-08 21:08:31 +07:00
|
|
|
|
|
|
|
err_spi:
|
|
|
|
#ifdef CONFIG_SERIAL_SC16IS7XX_I2C
|
|
|
|
i2c_del_driver(&sc16is7xx_i2c_uart_driver);
|
|
|
|
#endif
|
|
|
|
err_i2c:
|
|
|
|
uart_unregister_driver(&sc16is7xx_uart);
|
|
|
|
return ret;
|
2015-05-25 13:21:09 +07:00
|
|
|
}
|
|
|
|
module_init(sc16is7xx_init);
|
|
|
|
|
|
|
|
static void __exit sc16is7xx_exit(void)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_SERIAL_SC16IS7XX_I2C
|
|
|
|
i2c_del_driver(&sc16is7xx_i2c_uart_driver);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_SERIAL_SC16IS7XX_SPI
|
|
|
|
spi_unregister_driver(&sc16is7xx_spi_uart_driver);
|
|
|
|
#endif
|
2015-07-31 19:44:23 +07:00
|
|
|
uart_unregister_driver(&sc16is7xx_uart);
|
2015-05-25 13:21:09 +07:00
|
|
|
}
|
|
|
|
module_exit(sc16is7xx_exit);
|
2014-04-25 07:56:06 +07:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_AUTHOR("Jon Ringle <jringle@gridpoint.com>");
|
|
|
|
MODULE_DESCRIPTION("SC16IS7XX serial driver");
|