2018-03-15 07:58:49 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
// Copyright (c) 2017-2018, The Linux foundation. All rights reserved.
|
|
|
|
|
|
|
|
#include <linux/clk.h>
|
|
|
|
#include <linux/console.h>
|
|
|
|
#include <linux/io.h>
|
|
|
|
#include <linux/iopoll.h>
|
2019-10-10 17:46:03 +08:00
|
|
|
#include <linux/irq.h>
|
2018-03-15 07:58:49 +08:00
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/of.h>
|
|
|
|
#include <linux/of_device.h>
|
2020-06-15 20:02:39 +08:00
|
|
|
#include <linux/pm_opp.h>
|
2018-03-15 07:58:49 +08:00
|
|
|
#include <linux/platform_device.h>
|
2020-01-06 22:45:04 +08:00
|
|
|
#include <linux/pm_runtime.h>
|
2019-10-10 17:46:43 +08:00
|
|
|
#include <linux/pm_wakeirq.h>
|
2018-03-15 07:58:49 +08:00
|
|
|
#include <linux/qcom-geni-se.h>
|
|
|
|
#include <linux/serial.h>
|
|
|
|
#include <linux/serial_core.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/tty.h>
|
|
|
|
#include <linux/tty_flip.h>
|
|
|
|
|
|
|
|
/* UART specific GENI registers */
|
2018-07-14 06:17:35 +08:00
|
|
|
#define SE_UART_LOOPBACK_CFG 0x22c
|
2020-03-04 19:22:03 +08:00
|
|
|
#define SE_UART_IO_MACRO_CTRL 0x240
|
2018-03-15 07:58:49 +08:00
|
|
|
#define SE_UART_TX_TRANS_CFG 0x25c
|
|
|
|
#define SE_UART_TX_WORD_LEN 0x268
|
|
|
|
#define SE_UART_TX_STOP_BIT_LEN 0x26c
|
|
|
|
#define SE_UART_TX_TRANS_LEN 0x270
|
|
|
|
#define SE_UART_RX_TRANS_CFG 0x280
|
|
|
|
#define SE_UART_RX_WORD_LEN 0x28c
|
|
|
|
#define SE_UART_RX_STALE_CNT 0x294
|
|
|
|
#define SE_UART_TX_PARITY_CFG 0x2a4
|
|
|
|
#define SE_UART_RX_PARITY_CFG 0x2a8
|
2018-07-14 06:17:35 +08:00
|
|
|
#define SE_UART_MANUAL_RFR 0x2ac
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
/* SE_UART_TRANS_CFG */
|
|
|
|
#define UART_TX_PAR_EN BIT(0)
|
|
|
|
#define UART_CTS_MASK BIT(1)
|
|
|
|
|
|
|
|
/* SE_UART_TX_WORD_LEN */
|
|
|
|
#define TX_WORD_LEN_MSK GENMASK(9, 0)
|
|
|
|
|
|
|
|
/* SE_UART_TX_STOP_BIT_LEN */
|
|
|
|
#define TX_STOP_BIT_LEN_MSK GENMASK(23, 0)
|
|
|
|
#define TX_STOP_BIT_LEN_1 0
|
|
|
|
#define TX_STOP_BIT_LEN_1_5 1
|
|
|
|
#define TX_STOP_BIT_LEN_2 2
|
|
|
|
|
|
|
|
/* SE_UART_TX_TRANS_LEN */
|
|
|
|
#define TX_TRANS_LEN_MSK GENMASK(23, 0)
|
|
|
|
|
|
|
|
/* SE_UART_RX_TRANS_CFG */
|
|
|
|
#define UART_RX_INS_STATUS_BIT BIT(2)
|
|
|
|
#define UART_RX_PAR_EN BIT(3)
|
|
|
|
|
|
|
|
/* SE_UART_RX_WORD_LEN */
|
|
|
|
#define RX_WORD_LEN_MASK GENMASK(9, 0)
|
|
|
|
|
|
|
|
/* SE_UART_RX_STALE_CNT */
|
|
|
|
#define RX_STALE_CNT GENMASK(23, 0)
|
|
|
|
|
|
|
|
/* SE_UART_TX_PARITY_CFG/RX_PARITY_CFG */
|
|
|
|
#define PAR_CALC_EN BIT(0)
|
|
|
|
#define PAR_MODE_MSK GENMASK(2, 1)
|
|
|
|
#define PAR_MODE_SHFT 1
|
|
|
|
#define PAR_EVEN 0x00
|
|
|
|
#define PAR_ODD 0x01
|
|
|
|
#define PAR_SPACE 0x10
|
|
|
|
#define PAR_MARK 0x11
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
/* SE_UART_MANUAL_RFR register fields */
|
|
|
|
#define UART_MANUAL_RFR_EN BIT(31)
|
|
|
|
#define UART_RFR_NOT_READY BIT(1)
|
|
|
|
#define UART_RFR_READY BIT(0)
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
/* UART M_CMD OP codes */
|
|
|
|
#define UART_START_TX 0x1
|
|
|
|
#define UART_START_BREAK 0x4
|
|
|
|
#define UART_STOP_BREAK 0x5
|
|
|
|
/* UART S_CMD OP codes */
|
|
|
|
#define UART_START_READ 0x1
|
|
|
|
#define UART_PARAM 0x1
|
|
|
|
|
|
|
|
#define UART_OVERSAMPLING 32
|
|
|
|
#define STALE_TIMEOUT 16
|
|
|
|
#define DEFAULT_BITS_PER_CHAR 10
|
|
|
|
#define GENI_UART_CONS_PORTS 1
|
2018-07-14 06:17:35 +08:00
|
|
|
#define GENI_UART_PORTS 3
|
2018-03-15 07:58:49 +08:00
|
|
|
#define DEF_FIFO_DEPTH_WORDS 16
|
|
|
|
#define DEF_TX_WM 2
|
|
|
|
#define DEF_FIFO_WIDTH_BITS 32
|
2019-01-08 09:58:36 +08:00
|
|
|
#define UART_RX_WM 2
|
2020-01-06 22:45:05 +08:00
|
|
|
|
|
|
|
/* SE_UART_LOOPBACK_CFG */
|
|
|
|
#define RX_TX_SORTED BIT(0)
|
|
|
|
#define CTS_RTS_SORTED BIT(1)
|
|
|
|
#define RX_TX_CTS_RTS_SORTED (RX_TX_SORTED | CTS_RTS_SORTED)
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-03-04 19:22:03 +08:00
|
|
|
/* UART pin swap value */
|
|
|
|
#define DEFAULT_IO_MACRO_IO0_IO1_MASK GENMASK(3, 0)
|
|
|
|
#define IO_MACRO_IO0_SEL 0x3
|
|
|
|
#define DEFAULT_IO_MACRO_IO2_IO3_MASK GENMASK(15, 4)
|
|
|
|
#define IO_MACRO_IO2_IO3_SWAP 0x4640
|
|
|
|
|
2020-06-27 04:00:33 +08:00
|
|
|
/* We always configure 4 bytes per FIFO word */
|
|
|
|
#define BYTES_PER_FIFO_WORD 4
|
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
struct qcom_geni_private_data {
|
|
|
|
/* NOTE: earlycon port will have NULL here */
|
|
|
|
struct uart_driver *drv;
|
|
|
|
|
|
|
|
u32 poll_cached_bytes;
|
|
|
|
unsigned int poll_cached_bytes_cnt;
|
2020-06-27 04:00:33 +08:00
|
|
|
|
|
|
|
u32 write_cached_bytes;
|
|
|
|
unsigned int write_cached_bytes_cnt;
|
2020-06-27 04:00:32 +08:00
|
|
|
};
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
struct qcom_geni_serial_port {
|
|
|
|
struct uart_port uport;
|
|
|
|
struct geni_se se;
|
2020-01-06 22:45:04 +08:00
|
|
|
const char *name;
|
2018-03-15 07:58:49 +08:00
|
|
|
u32 tx_fifo_depth;
|
|
|
|
u32 tx_fifo_width;
|
|
|
|
u32 rx_fifo_depth;
|
|
|
|
bool setup;
|
|
|
|
int (*handle_rx)(struct uart_port *uport, u32 bytes, bool drop);
|
|
|
|
unsigned int baud;
|
2020-03-06 14:47:07 +08:00
|
|
|
void *rx_fifo;
|
2018-07-14 06:17:35 +08:00
|
|
|
u32 loopback;
|
2018-03-15 07:58:49 +08:00
|
|
|
bool brk;
|
2018-11-30 10:18:40 +08:00
|
|
|
|
|
|
|
unsigned int tx_remaining;
|
2019-10-10 17:46:43 +08:00
|
|
|
int wakeup_irq;
|
2020-03-04 19:22:03 +08:00
|
|
|
bool rx_tx_swap;
|
|
|
|
bool cts_rts_swap;
|
2020-06-27 04:00:32 +08:00
|
|
|
|
|
|
|
struct qcom_geni_private_data private_data;
|
2018-03-15 07:58:49 +08:00
|
|
|
};
|
|
|
|
|
2018-05-04 04:14:36 +08:00
|
|
|
static const struct uart_ops qcom_geni_console_pops;
|
2018-07-14 06:17:35 +08:00
|
|
|
static const struct uart_ops qcom_geni_uart_pops;
|
2018-03-15 07:58:49 +08:00
|
|
|
static struct uart_driver qcom_geni_console_driver;
|
2018-07-14 06:17:35 +08:00
|
|
|
static struct uart_driver qcom_geni_uart_driver;
|
2018-03-15 07:58:49 +08:00
|
|
|
static int handle_rx_console(struct uart_port *uport, u32 bytes, bool drop);
|
2018-07-14 06:17:35 +08:00
|
|
|
static int handle_rx_uart(struct uart_port *uport, u32 bytes, bool drop);
|
2018-03-15 07:58:49 +08:00
|
|
|
static unsigned int qcom_geni_serial_tx_empty(struct uart_port *port);
|
|
|
|
static void qcom_geni_serial_stop_rx(struct uart_port *uport);
|
2020-02-11 18:13:02 +08:00
|
|
|
static void qcom_geni_serial_handle_rx(struct uart_port *uport, bool drop);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
static const unsigned long root_freq[] = {7372800, 14745600, 19200000, 29491200,
|
2020-05-29 18:14:42 +08:00
|
|
|
32000000, 48000000, 51200000, 64000000,
|
|
|
|
80000000, 96000000, 100000000,
|
|
|
|
102400000, 112000000, 120000000,
|
|
|
|
128000000};
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
#define to_dev_port(ptr, member) \
|
|
|
|
container_of(ptr, struct qcom_geni_serial_port, member)
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
static struct qcom_geni_serial_port qcom_geni_uart_ports[GENI_UART_PORTS] = {
|
|
|
|
[0] = {
|
|
|
|
.uport = {
|
|
|
|
.iotype = UPIO_MEM,
|
|
|
|
.ops = &qcom_geni_uart_pops,
|
|
|
|
.flags = UPF_BOOT_AUTOCONF,
|
|
|
|
.line = 0,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
[1] = {
|
|
|
|
.uport = {
|
|
|
|
.iotype = UPIO_MEM,
|
|
|
|
.ops = &qcom_geni_uart_pops,
|
|
|
|
.flags = UPF_BOOT_AUTOCONF,
|
|
|
|
.line = 1,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
[2] = {
|
|
|
|
.uport = {
|
|
|
|
.iotype = UPIO_MEM,
|
|
|
|
.ops = &qcom_geni_uart_pops,
|
|
|
|
.flags = UPF_BOOT_AUTOCONF,
|
|
|
|
.line = 2,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2018-05-04 04:14:36 +08:00
|
|
|
static struct qcom_geni_serial_port qcom_geni_console_port = {
|
|
|
|
.uport = {
|
|
|
|
.iotype = UPIO_MEM,
|
|
|
|
.ops = &qcom_geni_console_pops,
|
|
|
|
.flags = UPF_BOOT_AUTOCONF,
|
|
|
|
.line = 0,
|
|
|
|
},
|
|
|
|
};
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
static int qcom_geni_serial_request_port(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
struct platform_device *pdev = to_platform_device(uport->dev);
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
|
2019-08-02 21:08:17 +08:00
|
|
|
uport->membase = devm_platform_ioremap_resource(pdev, 0);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (IS_ERR(uport->membase))
|
|
|
|
return PTR_ERR(uport->membase);
|
|
|
|
port->se.base = uport->membase;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_config_port(struct uart_port *uport, int cfg_flags)
|
|
|
|
{
|
|
|
|
if (cfg_flags & UART_CONFIG_TYPE) {
|
|
|
|
uport->type = PORT_MSM;
|
|
|
|
qcom_geni_serial_request_port(uport);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
static unsigned int qcom_geni_serial_get_mctrl(struct uart_port *uport)
|
2018-03-15 07:58:49 +08:00
|
|
|
{
|
2018-07-14 06:17:35 +08:00
|
|
|
unsigned int mctrl = TIOCM_DSR | TIOCM_CAR;
|
|
|
|
u32 geni_ios;
|
|
|
|
|
2019-01-19 08:23:05 +08:00
|
|
|
if (uart_console(uport)) {
|
2018-07-14 06:17:35 +08:00
|
|
|
mctrl |= TIOCM_CTS;
|
|
|
|
} else {
|
2019-01-08 09:58:35 +08:00
|
|
|
geni_ios = readl(uport->membase + SE_GENI_IOS);
|
2018-07-14 06:17:35 +08:00
|
|
|
if (!(geni_ios & IO2_DATA_IN))
|
|
|
|
mctrl |= TIOCM_CTS;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mctrl;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
static void qcom_geni_serial_set_mctrl(struct uart_port *uport,
|
2018-03-15 07:58:49 +08:00
|
|
|
unsigned int mctrl)
|
|
|
|
{
|
2018-07-14 06:17:35 +08:00
|
|
|
u32 uart_manual_rfr = 0;
|
2020-01-06 22:45:05 +08:00
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
2018-07-14 06:17:35 +08:00
|
|
|
|
2019-01-19 08:23:05 +08:00
|
|
|
if (uart_console(uport))
|
2018-07-14 06:17:35 +08:00
|
|
|
return;
|
|
|
|
|
2020-01-06 22:45:05 +08:00
|
|
|
if (mctrl & TIOCM_LOOP)
|
|
|
|
port->loopback = RX_TX_CTS_RTS_SORTED;
|
|
|
|
|
2020-09-10 20:53:58 +08:00
|
|
|
if (!(mctrl & TIOCM_RTS) && !uport->suspended)
|
2018-07-14 06:17:35 +08:00
|
|
|
uart_manual_rfr = UART_MANUAL_RFR_EN | UART_RFR_NOT_READY;
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(uart_manual_rfr, uport->membase + SE_UART_MANUAL_RFR);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *qcom_geni_serial_get_type(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
return "MSM";
|
|
|
|
}
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
static struct qcom_geni_serial_port *get_port_from_line(int line, bool console)
|
2018-03-15 07:58:49 +08:00
|
|
|
{
|
2018-07-14 06:17:35 +08:00
|
|
|
struct qcom_geni_serial_port *port;
|
|
|
|
int nr_ports = console ? GENI_UART_CONS_PORTS : GENI_UART_PORTS;
|
|
|
|
|
|
|
|
if (line < 0 || line >= nr_ports)
|
2018-03-15 07:58:49 +08:00
|
|
|
return ERR_PTR(-ENXIO);
|
2018-07-14 06:17:35 +08:00
|
|
|
|
|
|
|
port = console ? &qcom_geni_console_port : &qcom_geni_uart_ports[line];
|
|
|
|
return port;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool qcom_geni_serial_poll_bit(struct uart_port *uport,
|
|
|
|
int offset, int field, bool set)
|
|
|
|
{
|
|
|
|
u32 reg;
|
|
|
|
struct qcom_geni_serial_port *port;
|
|
|
|
unsigned int baud;
|
|
|
|
unsigned int fifo_bits;
|
|
|
|
unsigned long timeout_us = 20000;
|
2020-06-27 04:00:32 +08:00
|
|
|
struct qcom_geni_private_data *private_data = uport->private_data;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
if (private_data->drv) {
|
2018-03-15 07:58:49 +08:00
|
|
|
port = to_dev_port(uport, uport);
|
|
|
|
baud = port->baud;
|
|
|
|
if (!baud)
|
|
|
|
baud = 115200;
|
|
|
|
fifo_bits = port->tx_fifo_depth * port->tx_fifo_width;
|
|
|
|
/*
|
|
|
|
* Total polling iterations based on FIFO worth of bytes to be
|
|
|
|
* sent at current baud. Add a little fluff to the wait.
|
|
|
|
*/
|
|
|
|
timeout_us = ((fifo_bits * USEC_PER_SEC) / baud) + 500;
|
|
|
|
}
|
|
|
|
|
2018-05-04 04:14:40 +08:00
|
|
|
/*
|
|
|
|
* Use custom implementation instead of readl_poll_atomic since ktimer
|
|
|
|
* is not ready at the time of early console.
|
|
|
|
*/
|
|
|
|
timeout_us = DIV_ROUND_UP(timeout_us, 10) * 10;
|
|
|
|
while (timeout_us) {
|
2019-01-08 09:58:35 +08:00
|
|
|
reg = readl(uport->membase + offset);
|
2018-05-04 04:14:40 +08:00
|
|
|
if ((bool)(reg & field) == set)
|
|
|
|
return true;
|
|
|
|
udelay(10);
|
|
|
|
timeout_us -= 10;
|
|
|
|
}
|
|
|
|
return false;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_setup_tx(struct uart_port *uport, u32 xmit_size)
|
|
|
|
{
|
|
|
|
u32 m_cmd;
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(xmit_size, uport->membase + SE_UART_TX_TRANS_LEN);
|
2018-03-15 07:58:49 +08:00
|
|
|
m_cmd = UART_START_TX << M_OPCODE_SHFT;
|
|
|
|
writel(m_cmd, uport->membase + SE_GENI_M_CMD0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_poll_tx_done(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
int done;
|
|
|
|
u32 irq_clear = M_CMD_DONE_EN;
|
|
|
|
|
|
|
|
done = qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
|
|
|
|
M_CMD_DONE_EN, true);
|
|
|
|
if (!done) {
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(M_GENI_CMD_ABORT, uport->membase +
|
2018-03-15 07:58:49 +08:00
|
|
|
SE_GENI_M_CMD_CTRL_REG);
|
|
|
|
irq_clear |= M_CMD_ABORT_EN;
|
|
|
|
qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
|
|
|
|
M_CMD_ABORT_EN, true);
|
|
|
|
}
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(irq_clear, uport->membase + SE_GENI_M_IRQ_CLEAR);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_abort_rx(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
u32 irq_clear = S_CMD_DONE_EN | S_CMD_ABORT_EN;
|
|
|
|
|
|
|
|
writel(S_GENI_CMD_ABORT, uport->membase + SE_GENI_S_CMD_CTRL_REG);
|
|
|
|
qcom_geni_serial_poll_bit(uport, SE_GENI_S_CMD_CTRL_REG,
|
|
|
|
S_GENI_CMD_ABORT, false);
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(irq_clear, uport->membase + SE_GENI_S_IRQ_CLEAR);
|
|
|
|
writel(FORCE_DEFAULT, uport->membase + GENI_FORCE_DEFAULT_REG);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
2020-06-27 04:00:32 +08:00
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
static int qcom_geni_serial_get_char(struct uart_port *uport)
|
|
|
|
{
|
2020-06-27 04:00:32 +08:00
|
|
|
struct qcom_geni_private_data *private_data = uport->private_data;
|
2018-03-15 07:58:49 +08:00
|
|
|
u32 status;
|
2020-06-27 04:00:32 +08:00
|
|
|
u32 word_cnt;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!private_data->poll_cached_bytes_cnt) {
|
|
|
|
status = readl(uport->membase + SE_GENI_M_IRQ_STATUS);
|
|
|
|
writel(status, uport->membase + SE_GENI_M_IRQ_CLEAR);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
status = readl(uport->membase + SE_GENI_S_IRQ_STATUS);
|
|
|
|
writel(status, uport->membase + SE_GENI_S_IRQ_CLEAR);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
status = readl(uport->membase + SE_GENI_RX_FIFO_STATUS);
|
|
|
|
word_cnt = status & RX_FIFO_WC_MSK;
|
|
|
|
if (!word_cnt)
|
|
|
|
return NO_POLL_CHAR;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
if (word_cnt == 1 && (status & RX_LAST))
|
2020-08-07 13:19:08 +08:00
|
|
|
/*
|
|
|
|
* NOTE: If RX_LAST_BYTE_VALID is 0 it needs to be
|
|
|
|
* treated as if it was BYTES_PER_FIFO_WORD.
|
|
|
|
*/
|
2020-06-27 04:00:32 +08:00
|
|
|
private_data->poll_cached_bytes_cnt =
|
|
|
|
(status & RX_LAST_BYTE_VALID_MSK) >>
|
|
|
|
RX_LAST_BYTE_VALID_SHFT;
|
2020-08-07 13:19:08 +08:00
|
|
|
|
|
|
|
if (private_data->poll_cached_bytes_cnt == 0)
|
|
|
|
private_data->poll_cached_bytes_cnt = BYTES_PER_FIFO_WORD;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
private_data->poll_cached_bytes =
|
|
|
|
readl(uport->membase + SE_GENI_RX_FIFOn);
|
|
|
|
}
|
|
|
|
|
|
|
|
private_data->poll_cached_bytes_cnt--;
|
|
|
|
ret = private_data->poll_cached_bytes & 0xff;
|
|
|
|
private_data->poll_cached_bytes >>= 8;
|
|
|
|
|
|
|
|
return ret;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_poll_put_char(struct uart_port *uport,
|
|
|
|
unsigned char c)
|
|
|
|
{
|
2019-01-08 09:58:36 +08:00
|
|
|
writel(DEF_TX_WM, uport->membase + SE_GENI_TX_WATERMARK_REG);
|
2018-03-15 07:58:49 +08:00
|
|
|
qcom_geni_serial_setup_tx(uport, 1);
|
|
|
|
WARN_ON(!qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
|
|
|
|
M_TX_FIFO_WATERMARK_EN, true));
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(c, uport->membase + SE_GENI_TX_FIFOn);
|
|
|
|
writel(M_TX_FIFO_WATERMARK_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
|
2018-03-15 07:58:49 +08:00
|
|
|
qcom_geni_serial_poll_tx_done(uport);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE
|
|
|
|
static void qcom_geni_serial_wr_char(struct uart_port *uport, int ch)
|
|
|
|
{
|
2020-06-27 04:00:33 +08:00
|
|
|
struct qcom_geni_private_data *private_data = uport->private_data;
|
|
|
|
|
|
|
|
private_data->write_cached_bytes =
|
|
|
|
(private_data->write_cached_bytes >> 8) | (ch << 24);
|
|
|
|
private_data->write_cached_bytes_cnt++;
|
|
|
|
|
|
|
|
if (private_data->write_cached_bytes_cnt == BYTES_PER_FIFO_WORD) {
|
|
|
|
writel(private_data->write_cached_bytes,
|
|
|
|
uport->membase + SE_GENI_TX_FIFOn);
|
|
|
|
private_data->write_cached_bytes_cnt = 0;
|
|
|
|
}
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
__qcom_geni_serial_console_write(struct uart_port *uport, const char *s,
|
|
|
|
unsigned int count)
|
|
|
|
{
|
2020-06-27 04:00:33 +08:00
|
|
|
struct qcom_geni_private_data *private_data = uport->private_data;
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
int i;
|
|
|
|
u32 bytes_to_send = count;
|
|
|
|
|
|
|
|
for (i = 0; i < count; i++) {
|
2018-05-04 04:14:33 +08:00
|
|
|
/*
|
|
|
|
* uart_console_write() adds a carriage return for each newline.
|
|
|
|
* Account for additional bytes to be written.
|
|
|
|
*/
|
2018-03-15 07:58:49 +08:00
|
|
|
if (s[i] == '\n')
|
|
|
|
bytes_to_send++;
|
|
|
|
}
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(DEF_TX_WM, uport->membase + SE_GENI_TX_WATERMARK_REG);
|
2018-03-15 07:58:49 +08:00
|
|
|
qcom_geni_serial_setup_tx(uport, bytes_to_send);
|
|
|
|
for (i = 0; i < count; ) {
|
|
|
|
size_t chars_to_write = 0;
|
|
|
|
size_t avail = DEF_FIFO_DEPTH_WORDS - DEF_TX_WM;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the WM bit never set, then the Tx state machine is not
|
|
|
|
* in a valid state, so break, cancel/abort any existing
|
|
|
|
* command. Unfortunately the current data being written is
|
|
|
|
* lost.
|
|
|
|
*/
|
|
|
|
if (!qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
|
|
|
|
M_TX_FIFO_WATERMARK_EN, true))
|
|
|
|
break;
|
2018-05-04 04:14:34 +08:00
|
|
|
chars_to_write = min_t(size_t, count - i, avail / 2);
|
2018-03-15 07:58:49 +08:00
|
|
|
uart_console_write(uport, s + i, chars_to_write,
|
|
|
|
qcom_geni_serial_wr_char);
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(M_TX_FIFO_WATERMARK_EN, uport->membase +
|
2018-03-15 07:58:49 +08:00
|
|
|
SE_GENI_M_IRQ_CLEAR);
|
|
|
|
i += chars_to_write;
|
|
|
|
}
|
2020-06-27 04:00:33 +08:00
|
|
|
|
|
|
|
if (private_data->write_cached_bytes_cnt) {
|
|
|
|
private_data->write_cached_bytes >>= BITS_PER_BYTE *
|
|
|
|
(BYTES_PER_FIFO_WORD - private_data->write_cached_bytes_cnt);
|
|
|
|
writel(private_data->write_cached_bytes,
|
|
|
|
uport->membase + SE_GENI_TX_FIFOn);
|
|
|
|
private_data->write_cached_bytes_cnt = 0;
|
|
|
|
}
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
qcom_geni_serial_poll_tx_done(uport);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_console_write(struct console *co, const char *s,
|
|
|
|
unsigned int count)
|
|
|
|
{
|
|
|
|
struct uart_port *uport;
|
|
|
|
struct qcom_geni_serial_port *port;
|
|
|
|
bool locked = true;
|
|
|
|
unsigned long flags;
|
2018-11-30 10:18:40 +08:00
|
|
|
u32 geni_status;
|
2018-12-20 04:33:53 +08:00
|
|
|
u32 irq_en;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
WARN_ON(co->index < 0 || co->index >= GENI_UART_CONS_PORTS);
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
port = get_port_from_line(co->index, true);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (IS_ERR(port))
|
|
|
|
return;
|
|
|
|
|
|
|
|
uport = &port->uport;
|
|
|
|
if (oops_in_progress)
|
|
|
|
locked = spin_trylock_irqsave(&uport->lock, flags);
|
|
|
|
else
|
|
|
|
spin_lock_irqsave(&uport->lock, flags);
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
geni_status = readl(uport->membase + SE_GENI_STATUS);
|
2018-11-30 10:18:40 +08:00
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
/* Cancel the current write to log the fault */
|
|
|
|
if (!locked) {
|
|
|
|
geni_se_cancel_m_cmd(&port->se);
|
|
|
|
if (!qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
|
|
|
|
M_CMD_CANCEL_EN, true)) {
|
|
|
|
geni_se_abort_m_cmd(&port->se);
|
|
|
|
qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
|
|
|
|
M_CMD_ABORT_EN, true);
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(M_CMD_ABORT_EN, uport->membase +
|
2018-03-15 07:58:49 +08:00
|
|
|
SE_GENI_M_IRQ_CLEAR);
|
|
|
|
}
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(M_CMD_CANCEL_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
|
2018-11-30 10:18:40 +08:00
|
|
|
} else if ((geni_status & M_GENI_CMD_ACTIVE) && !port->tx_remaining) {
|
|
|
|
/*
|
|
|
|
* It seems we can't interrupt existing transfers if all data
|
|
|
|
* has been sent, in which case we need to look for done first.
|
|
|
|
*/
|
|
|
|
qcom_geni_serial_poll_tx_done(uport);
|
2018-12-20 04:33:53 +08:00
|
|
|
|
|
|
|
if (uart_circ_chars_pending(&uport->state->xmit)) {
|
2019-01-08 09:58:35 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
|
|
|
|
writel(irq_en | M_TX_FIFO_WATERMARK_EN,
|
2018-12-20 04:33:53 +08:00
|
|
|
uport->membase + SE_GENI_M_IRQ_EN);
|
|
|
|
}
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
__qcom_geni_serial_console_write(uport, s, count);
|
2018-11-30 10:18:40 +08:00
|
|
|
|
|
|
|
if (port->tx_remaining)
|
|
|
|
qcom_geni_serial_setup_tx(uport, port->tx_remaining);
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
if (locked)
|
|
|
|
spin_unlock_irqrestore(&uport->lock, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int handle_rx_console(struct uart_port *uport, u32 bytes, bool drop)
|
|
|
|
{
|
|
|
|
u32 i;
|
|
|
|
unsigned char buf[sizeof(u32)];
|
|
|
|
struct tty_port *tport;
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
|
|
|
|
tport = &uport->state->port;
|
|
|
|
for (i = 0; i < bytes; ) {
|
|
|
|
int c;
|
2020-06-27 04:00:33 +08:00
|
|
|
int chunk = min_t(int, bytes - i, BYTES_PER_FIFO_WORD);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, buf, 1);
|
|
|
|
i += chunk;
|
|
|
|
if (drop)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for (c = 0; c < chunk; c++) {
|
|
|
|
int sysrq;
|
|
|
|
|
|
|
|
uport->icount.rx++;
|
|
|
|
if (port->brk && buf[c] == 0) {
|
|
|
|
port->brk = false;
|
|
|
|
if (uart_handle_break(uport))
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-10-31 06:11:05 +08:00
|
|
|
sysrq = uart_prepare_sysrq_char(uport, buf[c]);
|
2018-10-31 06:11:03 +08:00
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
if (!sysrq)
|
|
|
|
tty_insert_flip_char(tport, buf[c], TTY_NORMAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!drop)
|
|
|
|
tty_flip_buffer_push(tport);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static int handle_rx_console(struct uart_port *uport, u32 bytes, bool drop)
|
|
|
|
{
|
|
|
|
return -EPERM;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_SERIAL_QCOM_GENI_CONSOLE */
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
static int handle_rx_uart(struct uart_port *uport, u32 bytes, bool drop)
|
|
|
|
{
|
|
|
|
struct tty_port *tport;
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
u32 num_bytes_pw = port->tx_fifo_width / BITS_PER_BYTE;
|
|
|
|
u32 words = ALIGN(bytes, num_bytes_pw) / num_bytes_pw;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
tport = &uport->state->port;
|
|
|
|
ioread32_rep(uport->membase + SE_GENI_RX_FIFOn, port->rx_fifo, words);
|
|
|
|
if (drop)
|
|
|
|
return 0;
|
|
|
|
|
2020-03-06 14:47:07 +08:00
|
|
|
ret = tty_insert_flip_string(tport, port->rx_fifo, bytes);
|
2018-07-14 06:17:35 +08:00
|
|
|
if (ret != bytes) {
|
|
|
|
dev_err(uport->dev, "%s:Unable to push data ret %d_bytes %d\n",
|
|
|
|
__func__, ret, bytes);
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
}
|
|
|
|
uport->icount.rx += ret;
|
|
|
|
tty_flip_buffer_push(tport);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
static void qcom_geni_serial_start_tx(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
u32 irq_en;
|
|
|
|
u32 status;
|
|
|
|
|
2019-01-08 09:58:37 +08:00
|
|
|
status = readl(uport->membase + SE_GENI_STATUS);
|
|
|
|
if (status & M_GENI_CMD_ACTIVE)
|
|
|
|
return;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-01-08 09:58:37 +08:00
|
|
|
if (!qcom_geni_serial_tx_empty(uport))
|
|
|
|
return;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-01-08 09:58:37 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
|
|
|
|
irq_en |= M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-01-08 09:58:37 +08:00
|
|
|
writel(DEF_TX_WM, uport->membase + SE_GENI_TX_WATERMARK_REG);
|
|
|
|
writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_stop_tx(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
u32 irq_en;
|
|
|
|
u32 status;
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
|
2019-01-08 09:58:37 +08:00
|
|
|
irq_en &= ~(M_CMD_DONE_EN | M_TX_FIFO_WATERMARK_EN);
|
|
|
|
writel(0, uport->membase + SE_GENI_TX_WATERMARK_REG);
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN);
|
|
|
|
status = readl(uport->membase + SE_GENI_STATUS);
|
2018-03-15 07:58:49 +08:00
|
|
|
/* Possible stop tx is called multiple times. */
|
|
|
|
if (!(status & M_GENI_CMD_ACTIVE))
|
|
|
|
return;
|
|
|
|
|
|
|
|
geni_se_cancel_m_cmd(&port->se);
|
|
|
|
if (!qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
|
|
|
|
M_CMD_CANCEL_EN, true)) {
|
|
|
|
geni_se_abort_m_cmd(&port->se);
|
|
|
|
qcom_geni_serial_poll_bit(uport, SE_GENI_M_IRQ_STATUS,
|
|
|
|
M_CMD_ABORT_EN, true);
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(M_CMD_ABORT_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(M_CMD_CANCEL_EN, uport->membase + SE_GENI_M_IRQ_CLEAR);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_start_rx(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
u32 irq_en;
|
|
|
|
u32 status;
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
status = readl(uport->membase + SE_GENI_STATUS);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (status & S_GENI_CMD_ACTIVE)
|
|
|
|
qcom_geni_serial_stop_rx(uport);
|
|
|
|
|
|
|
|
geni_se_setup_s_cmd(&port->se, UART_START_READ, 0);
|
|
|
|
|
2019-01-08 09:58:37 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_S_IRQ_EN);
|
|
|
|
irq_en |= S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN;
|
|
|
|
writel(irq_en, uport->membase + SE_GENI_S_IRQ_EN);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-01-08 09:58:37 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
|
|
|
|
irq_en |= M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN;
|
|
|
|
writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_stop_rx(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
u32 irq_en;
|
|
|
|
u32 status;
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
2020-02-11 18:13:02 +08:00
|
|
|
u32 s_irq_status;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-01-08 09:58:37 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_S_IRQ_EN);
|
|
|
|
irq_en &= ~(S_RX_FIFO_WATERMARK_EN | S_RX_FIFO_LAST_EN);
|
|
|
|
writel(irq_en, uport->membase + SE_GENI_S_IRQ_EN);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-01-08 09:58:37 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
|
|
|
|
irq_en &= ~(M_RX_FIFO_WATERMARK_EN | M_RX_FIFO_LAST_EN);
|
|
|
|
writel(irq_en, uport->membase + SE_GENI_M_IRQ_EN);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
status = readl(uport->membase + SE_GENI_STATUS);
|
2018-03-15 07:58:49 +08:00
|
|
|
/* Possible stop rx is called multiple times. */
|
|
|
|
if (!(status & S_GENI_CMD_ACTIVE))
|
|
|
|
return;
|
|
|
|
|
|
|
|
geni_se_cancel_s_cmd(&port->se);
|
2020-02-11 18:13:02 +08:00
|
|
|
qcom_geni_serial_poll_bit(uport, SE_GENI_S_IRQ_STATUS,
|
|
|
|
S_CMD_CANCEL_EN, true);
|
|
|
|
/*
|
|
|
|
* If timeout occurs secondary engine remains active
|
|
|
|
* and Abort sequence is executed.
|
|
|
|
*/
|
|
|
|
s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS);
|
|
|
|
/* Flush the Rx buffer */
|
|
|
|
if (s_irq_status & S_RX_FIFO_LAST_EN)
|
|
|
|
qcom_geni_serial_handle_rx(uport, true);
|
|
|
|
writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR);
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
status = readl(uport->membase + SE_GENI_STATUS);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (status & S_GENI_CMD_ACTIVE)
|
|
|
|
qcom_geni_serial_abort_rx(uport);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_handle_rx(struct uart_port *uport, bool drop)
|
|
|
|
{
|
|
|
|
u32 status;
|
|
|
|
u32 word_cnt;
|
|
|
|
u32 last_word_byte_cnt;
|
|
|
|
u32 last_word_partial;
|
|
|
|
u32 total_bytes;
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
status = readl(uport->membase + SE_GENI_RX_FIFO_STATUS);
|
2018-03-15 07:58:49 +08:00
|
|
|
word_cnt = status & RX_FIFO_WC_MSK;
|
|
|
|
last_word_partial = status & RX_LAST;
|
|
|
|
last_word_byte_cnt = (status & RX_LAST_BYTE_VALID_MSK) >>
|
|
|
|
RX_LAST_BYTE_VALID_SHFT;
|
|
|
|
|
|
|
|
if (!word_cnt)
|
|
|
|
return;
|
2020-06-27 04:00:33 +08:00
|
|
|
total_bytes = BYTES_PER_FIFO_WORD * (word_cnt - 1);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (last_word_partial && last_word_byte_cnt)
|
|
|
|
total_bytes += last_word_byte_cnt;
|
|
|
|
else
|
2020-06-27 04:00:33 +08:00
|
|
|
total_bytes += BYTES_PER_FIFO_WORD;
|
2018-03-15 07:58:49 +08:00
|
|
|
port->handle_rx(uport, total_bytes, drop);
|
|
|
|
}
|
|
|
|
|
2018-11-30 10:18:40 +08:00
|
|
|
static void qcom_geni_serial_handle_tx(struct uart_port *uport, bool done,
|
|
|
|
bool active)
|
2018-03-15 07:58:49 +08:00
|
|
|
{
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
struct circ_buf *xmit = &uport->state->xmit;
|
|
|
|
size_t avail;
|
|
|
|
size_t remaining;
|
2018-11-30 10:18:40 +08:00
|
|
|
size_t pending;
|
2018-03-15 07:58:49 +08:00
|
|
|
int i;
|
|
|
|
u32 status;
|
2018-12-14 03:43:20 +08:00
|
|
|
u32 irq_en;
|
2018-03-15 07:58:49 +08:00
|
|
|
unsigned int chunk;
|
|
|
|
int tail;
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
status = readl(uport->membase + SE_GENI_TX_FIFO_STATUS);
|
2018-11-30 10:18:40 +08:00
|
|
|
|
|
|
|
/* Complete the current tx command before taking newly added data */
|
|
|
|
if (active)
|
|
|
|
pending = port->tx_remaining;
|
|
|
|
else
|
|
|
|
pending = uart_circ_chars_pending(xmit);
|
|
|
|
|
|
|
|
/* All data has been transmitted and acknowledged as received */
|
|
|
|
if (!pending && !status && done) {
|
2018-03-15 07:58:49 +08:00
|
|
|
qcom_geni_serial_stop_tx(uport);
|
|
|
|
goto out_write_wakeup;
|
|
|
|
}
|
|
|
|
|
2018-11-30 10:18:40 +08:00
|
|
|
avail = port->tx_fifo_depth - (status & TX_FIFO_WC);
|
2020-06-27 04:00:33 +08:00
|
|
|
avail *= BYTES_PER_FIFO_WORD;
|
2018-07-14 06:17:35 +08:00
|
|
|
|
tty: serial: msm_geni_serial: Fix TX infinite loop
The GENI serial driver handled transmit by leaving stuff in the
common circular buffer until it had completely caught up to the head,
then clearing it out all at once. This is a suboptimal way to do
transmit, as it leaves data in the circular buffer that could be
freed. Moreover, the logic implementing it is wrong, and it is easy to
get into a situation where the UART infinitely writes out the same buffer.
I could reproduce infinite serial output of the same buffer by running
dmesg, then hitting Ctrl-C. I believe what happened is xmit_size was
something large, marching towards a larger value. Then the generic OS
code flushed out the buffer and replaced it with two characters. Now the
xmit_size is a large value marching towards a small value, which it wasn't
expecting. The driver subtracts xmit_size (very large) from
uart_circ_chars_pending (2), underflows, and repeats ad nauseum. The
locking isn't wrong here, as the locks are held whenever the buffer is
manipulated, it's just that the driver wasn't expecting the buffer to be
flushed out from underneath it in between transmits.
This change reworks transmit to grab what it can from the circular buffer,
and then update ->tail, both fixing the underflow and freeing up space
for a smoother circular experience.
Signed-off-by: Evan Green <evgreen@chromium.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-05-10 04:44:29 +08:00
|
|
|
tail = xmit->tail;
|
2018-12-20 02:17:47 +08:00
|
|
|
chunk = min(avail, pending);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (!chunk)
|
|
|
|
goto out_write_wakeup;
|
|
|
|
|
2018-11-30 10:18:40 +08:00
|
|
|
if (!port->tx_remaining) {
|
|
|
|
qcom_geni_serial_setup_tx(uport, pending);
|
|
|
|
port->tx_remaining = pending;
|
2018-12-14 03:43:20 +08:00
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
|
2018-12-14 03:43:20 +08:00
|
|
|
if (!(irq_en & M_TX_FIFO_WATERMARK_EN))
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(irq_en | M_TX_FIFO_WATERMARK_EN,
|
2018-12-14 03:43:20 +08:00
|
|
|
uport->membase + SE_GENI_M_IRQ_EN);
|
2018-11-30 10:18:40 +08:00
|
|
|
}
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
remaining = chunk;
|
|
|
|
for (i = 0; i < chunk; ) {
|
|
|
|
unsigned int tx_bytes;
|
2018-05-04 04:14:38 +08:00
|
|
|
u8 buf[sizeof(u32)];
|
2018-03-15 07:58:49 +08:00
|
|
|
int c;
|
|
|
|
|
2020-06-24 21:27:44 +08:00
|
|
|
memset(buf, 0, sizeof(buf));
|
2020-06-27 04:00:33 +08:00
|
|
|
tx_bytes = min_t(size_t, remaining, BYTES_PER_FIFO_WORD);
|
2018-12-20 02:17:47 +08:00
|
|
|
|
|
|
|
for (c = 0; c < tx_bytes ; c++) {
|
|
|
|
buf[c] = xmit->buf[tail++];
|
|
|
|
tail &= UART_XMIT_SIZE - 1;
|
|
|
|
}
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2018-05-04 04:14:38 +08:00
|
|
|
iowrite32_rep(uport->membase + SE_GENI_TX_FIFOn, buf, 1);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
i += tx_bytes;
|
|
|
|
uport->icount.tx += tx_bytes;
|
|
|
|
remaining -= tx_bytes;
|
2018-11-30 10:18:40 +08:00
|
|
|
port->tx_remaining -= tx_bytes;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
tty: serial: msm_geni_serial: Fix TX infinite loop
The GENI serial driver handled transmit by leaving stuff in the
common circular buffer until it had completely caught up to the head,
then clearing it out all at once. This is a suboptimal way to do
transmit, as it leaves data in the circular buffer that could be
freed. Moreover, the logic implementing it is wrong, and it is easy to
get into a situation where the UART infinitely writes out the same buffer.
I could reproduce infinite serial output of the same buffer by running
dmesg, then hitting Ctrl-C. I believe what happened is xmit_size was
something large, marching towards a larger value. Then the generic OS
code flushed out the buffer and replaced it with two characters. Now the
xmit_size is a large value marching towards a small value, which it wasn't
expecting. The driver subtracts xmit_size (very large) from
uart_circ_chars_pending (2), underflows, and repeats ad nauseum. The
locking isn't wrong here, as the locks are held whenever the buffer is
manipulated, it's just that the driver wasn't expecting the buffer to be
flushed out from underneath it in between transmits.
This change reworks transmit to grab what it can from the circular buffer,
and then update ->tail, both fixing the underflow and freeing up space
for a smoother circular experience.
Signed-off-by: Evan Green <evgreen@chromium.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-05-10 04:44:29 +08:00
|
|
|
|
2018-12-20 02:17:47 +08:00
|
|
|
xmit->tail = tail;
|
2018-12-14 03:43:20 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* The tx fifo watermark is level triggered and latched. Though we had
|
|
|
|
* cleared it in qcom_geni_serial_isr it will have already reasserted
|
|
|
|
* so we must clear it again here after our writes.
|
|
|
|
*/
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(M_TX_FIFO_WATERMARK_EN,
|
2018-12-14 03:43:20 +08:00
|
|
|
uport->membase + SE_GENI_M_IRQ_CLEAR);
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
out_write_wakeup:
|
2018-12-14 03:43:20 +08:00
|
|
|
if (!port->tx_remaining) {
|
2019-01-08 09:58:35 +08:00
|
|
|
irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
|
2018-12-14 03:43:20 +08:00
|
|
|
if (irq_en & M_TX_FIFO_WATERMARK_EN)
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(irq_en & ~M_TX_FIFO_WATERMARK_EN,
|
2018-12-14 03:43:20 +08:00
|
|
|
uport->membase + SE_GENI_M_IRQ_EN);
|
|
|
|
}
|
|
|
|
|
tty: serial: msm_geni_serial: Fix TX infinite loop
The GENI serial driver handled transmit by leaving stuff in the
common circular buffer until it had completely caught up to the head,
then clearing it out all at once. This is a suboptimal way to do
transmit, as it leaves data in the circular buffer that could be
freed. Moreover, the logic implementing it is wrong, and it is easy to
get into a situation where the UART infinitely writes out the same buffer.
I could reproduce infinite serial output of the same buffer by running
dmesg, then hitting Ctrl-C. I believe what happened is xmit_size was
something large, marching towards a larger value. Then the generic OS
code flushed out the buffer and replaced it with two characters. Now the
xmit_size is a large value marching towards a small value, which it wasn't
expecting. The driver subtracts xmit_size (very large) from
uart_circ_chars_pending (2), underflows, and repeats ad nauseum. The
locking isn't wrong here, as the locks are held whenever the buffer is
manipulated, it's just that the driver wasn't expecting the buffer to be
flushed out from underneath it in between transmits.
This change reworks transmit to grab what it can from the circular buffer,
and then update ->tail, both fixing the underflow and freeing up space
for a smoother circular experience.
Signed-off-by: Evan Green <evgreen@chromium.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-05-10 04:44:29 +08:00
|
|
|
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
|
|
|
|
uart_write_wakeup(uport);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static irqreturn_t qcom_geni_serial_isr(int isr, void *dev)
|
|
|
|
{
|
2019-01-08 09:58:38 +08:00
|
|
|
u32 m_irq_en;
|
|
|
|
u32 m_irq_status;
|
|
|
|
u32 s_irq_status;
|
|
|
|
u32 geni_status;
|
2018-03-15 07:58:49 +08:00
|
|
|
struct uart_port *uport = dev;
|
|
|
|
unsigned long flags;
|
|
|
|
bool drop_rx = false;
|
|
|
|
struct tty_port *tport = &uport->state->port;
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
|
|
|
|
if (uport->suspended)
|
2018-05-04 04:14:39 +08:00
|
|
|
return IRQ_NONE;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
spin_lock_irqsave(&uport->lock, flags);
|
2019-01-08 09:58:35 +08:00
|
|
|
m_irq_status = readl(uport->membase + SE_GENI_M_IRQ_STATUS);
|
|
|
|
s_irq_status = readl(uport->membase + SE_GENI_S_IRQ_STATUS);
|
|
|
|
geni_status = readl(uport->membase + SE_GENI_STATUS);
|
|
|
|
m_irq_en = readl(uport->membase + SE_GENI_M_IRQ_EN);
|
|
|
|
writel(m_irq_status, uport->membase + SE_GENI_M_IRQ_CLEAR);
|
|
|
|
writel(s_irq_status, uport->membase + SE_GENI_S_IRQ_CLEAR);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
if (WARN_ON(m_irq_status & M_ILLEGAL_CMD_EN))
|
|
|
|
goto out_unlock;
|
|
|
|
|
|
|
|
if (s_irq_status & S_RX_FIFO_WR_ERR_EN) {
|
|
|
|
uport->icount.overrun++;
|
|
|
|
tty_insert_flip_char(tport, 0, TTY_OVERRUN);
|
|
|
|
}
|
|
|
|
|
2018-12-14 03:43:20 +08:00
|
|
|
if (m_irq_status & m_irq_en & (M_TX_FIFO_WATERMARK_EN | M_CMD_DONE_EN))
|
2018-11-30 10:18:40 +08:00
|
|
|
qcom_geni_serial_handle_tx(uport, m_irq_status & M_CMD_DONE_EN,
|
|
|
|
geni_status & M_GENI_CMD_ACTIVE);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
if (s_irq_status & S_GP_IRQ_0_EN || s_irq_status & S_GP_IRQ_1_EN) {
|
|
|
|
if (s_irq_status & S_GP_IRQ_0_EN)
|
|
|
|
uport->icount.parity++;
|
|
|
|
drop_rx = true;
|
|
|
|
} else if (s_irq_status & S_GP_IRQ_2_EN ||
|
|
|
|
s_irq_status & S_GP_IRQ_3_EN) {
|
|
|
|
uport->icount.brk++;
|
|
|
|
port->brk = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s_irq_status & S_RX_FIFO_WATERMARK_EN ||
|
|
|
|
s_irq_status & S_RX_FIFO_LAST_EN)
|
|
|
|
qcom_geni_serial_handle_rx(uport, drop_rx);
|
|
|
|
|
|
|
|
out_unlock:
|
2018-10-31 06:11:05 +08:00
|
|
|
uart_unlock_and_check_sysrq(uport, flags);
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
return IRQ_HANDLED;
|
|
|
|
}
|
|
|
|
|
2018-05-04 04:14:34 +08:00
|
|
|
static void get_tx_fifo_size(struct qcom_geni_serial_port *port)
|
2018-03-15 07:58:49 +08:00
|
|
|
{
|
|
|
|
struct uart_port *uport;
|
|
|
|
|
|
|
|
uport = &port->uport;
|
|
|
|
port->tx_fifo_depth = geni_se_get_tx_fifo_depth(&port->se);
|
|
|
|
port->tx_fifo_width = geni_se_get_tx_fifo_width(&port->se);
|
|
|
|
port->rx_fifo_depth = geni_se_get_rx_fifo_depth(&port->se);
|
|
|
|
uport->fifosize =
|
|
|
|
(port->tx_fifo_depth * port->tx_fifo_width) / BITS_PER_BYTE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void qcom_geni_serial_shutdown(struct uart_port *uport)
|
|
|
|
{
|
2019-10-10 17:46:03 +08:00
|
|
|
disable_irq(uport->irq);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int qcom_geni_serial_port_setup(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
2019-01-08 09:58:38 +08:00
|
|
|
u32 rxstale = DEFAULT_BITS_PER_CHAR * STALE_TIMEOUT;
|
2018-09-06 04:11:46 +08:00
|
|
|
u32 proto;
|
2020-03-04 19:22:03 +08:00
|
|
|
u32 pin_swap;
|
2018-09-06 04:11:46 +08:00
|
|
|
|
|
|
|
proto = geni_se_read_proto(&port->se);
|
|
|
|
if (proto != GENI_SE_UART) {
|
|
|
|
dev_err(uport->dev, "Invalid FW loaded, proto: %d\n", proto);
|
|
|
|
return -ENXIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
qcom_geni_serial_stop_rx(uport);
|
|
|
|
|
|
|
|
get_tx_fifo_size(port);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(rxstale, uport->membase + SE_UART_RX_STALE_CNT);
|
2020-03-04 19:22:03 +08:00
|
|
|
|
|
|
|
pin_swap = readl(uport->membase + SE_UART_IO_MACRO_CTRL);
|
|
|
|
if (port->rx_tx_swap) {
|
|
|
|
pin_swap &= ~DEFAULT_IO_MACRO_IO2_IO3_MASK;
|
|
|
|
pin_swap |= IO_MACRO_IO2_IO3_SWAP;
|
|
|
|
}
|
|
|
|
if (port->cts_rts_swap) {
|
|
|
|
pin_swap &= ~DEFAULT_IO_MACRO_IO0_IO1_MASK;
|
|
|
|
pin_swap |= IO_MACRO_IO0_SEL;
|
|
|
|
}
|
|
|
|
/* Configure this register if RX-TX, CTS-RTS pins are swapped */
|
|
|
|
if (port->rx_tx_swap || port->cts_rts_swap)
|
|
|
|
writel(pin_swap, uport->membase + SE_UART_IO_MACRO_CTRL);
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
/*
|
|
|
|
* Make an unconditional cancel on the main sequencer to reset
|
|
|
|
* it else we could end up in data loss scenarios.
|
|
|
|
*/
|
2018-07-14 06:17:35 +08:00
|
|
|
if (uart_console(uport))
|
|
|
|
qcom_geni_serial_poll_tx_done(uport);
|
2020-06-27 04:00:33 +08:00
|
|
|
geni_se_config_packing(&port->se, BITS_PER_BYTE, BYTES_PER_FIFO_WORD,
|
|
|
|
false, true, true);
|
2019-01-08 09:58:36 +08:00
|
|
|
geni_se_init(&port->se, UART_RX_WM, port->rx_fifo_depth - 2);
|
2019-01-08 09:58:37 +08:00
|
|
|
geni_se_select_mode(&port->se, GENI_SE_FIFO);
|
2018-03-15 07:58:49 +08:00
|
|
|
port->setup = true;
|
2018-09-06 04:11:46 +08:00
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int qcom_geni_serial_startup(struct uart_port *uport)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
|
|
|
|
if (!port->setup) {
|
|
|
|
ret = qcom_geni_serial_port_setup(uport);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
2019-10-10 17:46:03 +08:00
|
|
|
enable_irq(uport->irq);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2019-10-10 17:46:03 +08:00
|
|
|
return 0;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned long get_clk_cfg(unsigned long clk_freq)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(root_freq); i++) {
|
|
|
|
if (!(root_freq[i] % clk_freq))
|
|
|
|
return root_freq[i];
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-08-01 20:11:53 +08:00
|
|
|
static unsigned long get_clk_div_rate(unsigned int baud,
|
|
|
|
unsigned int sampling_rate, unsigned int *clk_div)
|
2018-03-15 07:58:49 +08:00
|
|
|
{
|
|
|
|
unsigned long ser_clk;
|
|
|
|
unsigned long desired_clk;
|
|
|
|
|
2019-08-01 20:11:53 +08:00
|
|
|
desired_clk = baud * sampling_rate;
|
2018-03-15 07:58:49 +08:00
|
|
|
ser_clk = get_clk_cfg(desired_clk);
|
|
|
|
if (!ser_clk) {
|
|
|
|
pr_err("%s: Can't find matching DFS entry for baud %d\n",
|
|
|
|
__func__, baud);
|
|
|
|
return ser_clk;
|
|
|
|
}
|
|
|
|
|
|
|
|
*clk_div = ser_clk / desired_clk;
|
|
|
|
return ser_clk;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void qcom_geni_serial_set_termios(struct uart_port *uport,
|
|
|
|
struct ktermios *termios, struct ktermios *old)
|
|
|
|
{
|
|
|
|
unsigned int baud;
|
2019-01-08 09:58:38 +08:00
|
|
|
u32 bits_per_char;
|
|
|
|
u32 tx_trans_cfg;
|
|
|
|
u32 tx_parity_cfg;
|
|
|
|
u32 rx_trans_cfg;
|
|
|
|
u32 rx_parity_cfg;
|
|
|
|
u32 stop_bit_len;
|
2018-03-15 07:58:49 +08:00
|
|
|
unsigned int clk_div;
|
2019-01-08 09:58:38 +08:00
|
|
|
u32 ser_clk_cfg;
|
2018-03-15 07:58:49 +08:00
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
unsigned long clk_rate;
|
2019-08-01 20:11:53 +08:00
|
|
|
u32 ver, sampling_rate;
|
2020-06-23 18:38:53 +08:00
|
|
|
unsigned int avg_bw_core;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
qcom_geni_serial_stop_rx(uport);
|
|
|
|
/* baud rate */
|
|
|
|
baud = uart_get_baud_rate(uport, termios, old, 300, 4000000);
|
|
|
|
port->baud = baud;
|
2019-08-01 20:11:53 +08:00
|
|
|
|
|
|
|
sampling_rate = UART_OVERSAMPLING;
|
|
|
|
/* Sampling rate is halved for IP versions >= 2.5 */
|
|
|
|
ver = geni_se_get_qup_hw_version(&port->se);
|
2020-09-30 14:05:26 +08:00
|
|
|
if (ver >= QUP_SE_VERSION_2_5)
|
2019-08-01 20:11:53 +08:00
|
|
|
sampling_rate /= 2;
|
|
|
|
|
|
|
|
clk_rate = get_clk_div_rate(baud, sampling_rate, &clk_div);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (!clk_rate)
|
|
|
|
goto out_restart_rx;
|
|
|
|
|
|
|
|
uport->uartclk = clk_rate;
|
2020-06-15 20:02:39 +08:00
|
|
|
dev_pm_opp_set_rate(uport->dev, clk_rate);
|
2018-03-15 07:58:49 +08:00
|
|
|
ser_clk_cfg = SER_CLK_EN;
|
|
|
|
ser_clk_cfg |= clk_div << CLK_DIV_SHFT;
|
|
|
|
|
2020-06-23 18:38:53 +08:00
|
|
|
/*
|
|
|
|
* Bump up BW vote on CPU and CORE path as driver supports FIFO mode
|
|
|
|
* only.
|
|
|
|
*/
|
|
|
|
avg_bw_core = (baud > 115200) ? Bps_to_icc(CORE_2X_50_MHZ)
|
|
|
|
: GENI_DEFAULT_BW;
|
|
|
|
port->se.icc_paths[GENI_TO_CORE].avg_bw = avg_bw_core;
|
|
|
|
port->se.icc_paths[CPU_TO_GENI].avg_bw = Bps_to_icc(baud);
|
|
|
|
geni_icc_set_bw(&port->se);
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
/* parity */
|
2019-01-08 09:58:35 +08:00
|
|
|
tx_trans_cfg = readl(uport->membase + SE_UART_TX_TRANS_CFG);
|
|
|
|
tx_parity_cfg = readl(uport->membase + SE_UART_TX_PARITY_CFG);
|
|
|
|
rx_trans_cfg = readl(uport->membase + SE_UART_RX_TRANS_CFG);
|
|
|
|
rx_parity_cfg = readl(uport->membase + SE_UART_RX_PARITY_CFG);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (termios->c_cflag & PARENB) {
|
|
|
|
tx_trans_cfg |= UART_TX_PAR_EN;
|
|
|
|
rx_trans_cfg |= UART_RX_PAR_EN;
|
|
|
|
tx_parity_cfg |= PAR_CALC_EN;
|
|
|
|
rx_parity_cfg |= PAR_CALC_EN;
|
|
|
|
if (termios->c_cflag & PARODD) {
|
|
|
|
tx_parity_cfg |= PAR_ODD;
|
|
|
|
rx_parity_cfg |= PAR_ODD;
|
|
|
|
} else if (termios->c_cflag & CMSPAR) {
|
|
|
|
tx_parity_cfg |= PAR_SPACE;
|
|
|
|
rx_parity_cfg |= PAR_SPACE;
|
|
|
|
} else {
|
|
|
|
tx_parity_cfg |= PAR_EVEN;
|
|
|
|
rx_parity_cfg |= PAR_EVEN;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
tx_trans_cfg &= ~UART_TX_PAR_EN;
|
|
|
|
rx_trans_cfg &= ~UART_RX_PAR_EN;
|
|
|
|
tx_parity_cfg &= ~PAR_CALC_EN;
|
|
|
|
rx_parity_cfg &= ~PAR_CALC_EN;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bits per char */
|
|
|
|
switch (termios->c_cflag & CSIZE) {
|
|
|
|
case CS5:
|
|
|
|
bits_per_char = 5;
|
|
|
|
break;
|
|
|
|
case CS6:
|
|
|
|
bits_per_char = 6;
|
|
|
|
break;
|
|
|
|
case CS7:
|
|
|
|
bits_per_char = 7;
|
|
|
|
break;
|
|
|
|
case CS8:
|
|
|
|
default:
|
|
|
|
bits_per_char = 8;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* stop bits */
|
|
|
|
if (termios->c_cflag & CSTOPB)
|
|
|
|
stop_bit_len = TX_STOP_BIT_LEN_2;
|
|
|
|
else
|
|
|
|
stop_bit_len = TX_STOP_BIT_LEN_1;
|
|
|
|
|
|
|
|
/* flow control, clear the CTS_MASK bit if using flow control. */
|
|
|
|
if (termios->c_cflag & CRTSCTS)
|
|
|
|
tx_trans_cfg &= ~UART_CTS_MASK;
|
|
|
|
else
|
|
|
|
tx_trans_cfg |= UART_CTS_MASK;
|
|
|
|
|
|
|
|
if (baud)
|
|
|
|
uart_update_timeout(uport, termios->c_cflag, baud);
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
if (!uart_console(uport))
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(port->loopback,
|
2018-07-14 06:17:35 +08:00
|
|
|
uport->membase + SE_UART_LOOPBACK_CFG);
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(tx_trans_cfg, uport->membase + SE_UART_TX_TRANS_CFG);
|
|
|
|
writel(tx_parity_cfg, uport->membase + SE_UART_TX_PARITY_CFG);
|
|
|
|
writel(rx_trans_cfg, uport->membase + SE_UART_RX_TRANS_CFG);
|
|
|
|
writel(rx_parity_cfg, uport->membase + SE_UART_RX_PARITY_CFG);
|
|
|
|
writel(bits_per_char, uport->membase + SE_UART_TX_WORD_LEN);
|
|
|
|
writel(bits_per_char, uport->membase + SE_UART_RX_WORD_LEN);
|
|
|
|
writel(stop_bit_len, uport->membase + SE_UART_TX_STOP_BIT_LEN);
|
|
|
|
writel(ser_clk_cfg, uport->membase + GENI_SER_M_CLK_CFG);
|
|
|
|
writel(ser_clk_cfg, uport->membase + GENI_SER_S_CLK_CFG);
|
2018-03-15 07:58:49 +08:00
|
|
|
out_restart_rx:
|
|
|
|
qcom_geni_serial_start_rx(uport);
|
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned int qcom_geni_serial_tx_empty(struct uart_port *uport)
|
|
|
|
{
|
2018-05-04 04:14:37 +08:00
|
|
|
return !readl(uport->membase + SE_GENI_TX_FIFO_STATUS);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_SERIAL_QCOM_GENI_CONSOLE
|
tty: serial: qcom_geni_serial: Drop __init from qcom_geni_console_setup
When booting with heavily modularized config, the serial console
may not be able to load until after init when modules that
satisfy needed dependencies have time to load.
Unfortunately, as qcom_geni_console_setup is marked as __init,
the function may have been freed before we get to run it,
causing boot time crashes such as:
[ 6.469057] Unable to handle kernel paging request at virtual address ffffffe645d4e6cc
[ 6.481623] Mem abort info:
[ 6.484466] ESR = 0x86000007
[ 6.487557] EC = 0x21: IABT (current EL), IL = 32 bits
[ 6.492929] SET = 0, FnV = 0g
[ 6.496016] EA = 0, S1PTW = 0
[ 6.499202] swapper pgtable: 4k pages, 39-bit VAs, pgdp=000000008151e000
[ 6.501286] ufshcd-qcom 1d84000.ufshc: ufshcd_print_pwr_info:[RX, TX]: gear=[3, 3], lane[2, 2], pwr[FAST MODE, FAST MODE], rate = 2
[ 6.505977] [ffffffe645d4e6cc] pgd=000000017df9f003, p4d=000000017df9f003, pud=000000017df9f003, pmd=000000017df9c003, pte=0000000000000000
[ 6.505990] Internal error: Oops: 86000007 [#1] PREEMPT SMP
[ 6.505995] Modules linked in: zl10353 zl10039 zl10036 zd1301_demod xc5000 xc4000 ves1x93 ves1820 tuner_xc2028 tuner_simple tuner_types tua9001 tua6100 1
[ 6.506152] isl6405
[ 6.518104] ufshcd-qcom 1d84000.ufshc: ufshcd_find_max_sup_active_icc_level: Regulator capability was not set, actvIccLevel=0
[ 6.530549] horus3a helene fc2580 fc0013 fc0012 fc0011 ec100 e4000 dvb_pll ds3000 drxk drxd drx39xyj dib9000 dib8000 dib7000p dib7000m dib3000mc dibx003
[ 6.624271] CPU: 7 PID: 148 Comm: kworker/7:2 Tainted: G W 5.8.0-mainline-12021-g6defd37ba1cd #3455
[ 6.624273] Hardware name: Thundercomm Dragonboard 845c (DT)
[ 6.624290] Workqueue: events deferred_probe_work_func
[ 6.624296] pstate: 40c00005 (nZcv daif +PAN +UAO BTYPE=--)
[ 6.624307] pc : qcom_geni_console_setup+0x0/0x110
[ 6.624316] lr : try_enable_new_console+0xa0/0x140
[ 6.624318] sp : ffffffc010843a30
[ 6.624320] x29: ffffffc010843a30 x28: ffffffe645c3e7d0
[ 6.624325] x27: ffffff80f8022180 x26: ffffffc010843b28
[ 6.637937] x25: 0000000000000000 x24: ffffffe6462a2000
[ 6.637941] x23: ffffffe646398000 x22: 0000000000000000
[ 6.637945] x21: 0000000000000000 x20: ffffffe6462a5ce8
[ 6.637952] x19: ffffffe646398e38 x18: ffffffffffffffff
[ 6.680296] x17: 0000000000000000 x16: ffffffe64492b900
[ 6.680300] x15: ffffffe6461e9d08 x14: 69202930203d2064
[ 6.680305] x13: 7561625f65736162 x12: 202c363331203d20
[ 6.696434] x11: 0000000000000030 x10: 0101010101010101
[ 6.696438] x9 : 4d4d20746120304d x8 : 7f7f7f7f7f7f7f7f
[ 6.707249] x7 : feff4c524c787373 x6 : 0000000000008080
[ 6.707253] x5 : 0000000000000000 x4 : 8080000000000000
[ 6.707257] x3 : 0000000000000000 x2 : ffffffe645d4e6cc
[ 6.744223] qcom_geni_serial 898000.serial: dev_pm_opp_set_rate: failed to find OPP for freq 102400000 (-34)
[ 6.744966] x1 : fffffffefe74e174 x0 : ffffffe6462a5ce8
[ 6.753580] qcom_geni_serial 898000.serial: dev_pm_opp_set_rate: failed to find OPP for freq 102400000 (-34)
[ 6.761634] Call trace:
[ 6.761639] qcom_geni_console_setup+0x0/0x110
[ 6.761645] register_console+0x29c/0x2f8
[ 6.767981] Bluetooth: hci0: Frame reassembly failed (-84)
[ 6.775252] uart_add_one_port+0x438/0x500
[ 6.775258] qcom_geni_serial_probe+0x2c4/0x4a8
[ 6.775266] platform_drv_probe+0x58/0xa8
[ 6.855359] really_probe+0xec/0x398
[ 6.855362] driver_probe_device+0x5c/0xb8
[ 6.855367] __device_attach_driver+0x98/0xb8
[ 7.184945] bus_for_each_drv+0x74/0xd8
[ 7.188825] __device_attach+0xec/0x148
[ 7.192705] device_initial_probe+0x24/0x30
[ 7.196937] bus_probe_device+0x9c/0xa8
[ 7.200816] deferred_probe_work_func+0x7c/0xb8
[ 7.205398] process_one_work+0x20c/0x4b0
[ 7.209456] worker_thread+0x48/0x460
[ 7.213157] kthread+0x14c/0x158
[ 7.216432] ret_from_fork+0x10/0x18
[ 7.220049] Code: bad PC value
[ 7.223139] ---[ end trace 73f3b21e251d5a70 ]---
Thus this patch removes the __init avoiding crash in such
configs.
Cc: Andy Gross <agross@kernel.org>
Cc: Jiri Slaby <jirislaby@kernel.org>
Cc: Saravana Kannan <saravanak@google.com>
Cc: Todd Kjos <tkjos@google.com>
Cc: Amit Pundir <amit.pundir@linaro.org>
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-serial@vger.kernel.org
Suggested-by: Saravana Kannan <saravanak@google.com>
Signed-off-by: John Stultz <john.stultz@linaro.org>
Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org>
Link: https://lore.kernel.org/r/20200811025044.70626-1-john.stultz@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2020-08-11 10:50:44 +08:00
|
|
|
static int qcom_geni_console_setup(struct console *co, char *options)
|
2018-03-15 07:58:49 +08:00
|
|
|
{
|
|
|
|
struct uart_port *uport;
|
|
|
|
struct qcom_geni_serial_port *port;
|
2020-09-11 23:00:57 +08:00
|
|
|
int baud = 115200;
|
2018-03-15 07:58:49 +08:00
|
|
|
int bits = 8;
|
|
|
|
int parity = 'n';
|
|
|
|
int flow = 'n';
|
2018-09-06 04:11:46 +08:00
|
|
|
int ret;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
|
|
|
if (co->index >= GENI_UART_CONS_PORTS || co->index < 0)
|
|
|
|
return -ENXIO;
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
port = get_port_from_line(co->index, true);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (IS_ERR(port)) {
|
2018-05-04 04:14:34 +08:00
|
|
|
pr_err("Invalid line %d\n", co->index);
|
2018-03-15 07:58:49 +08:00
|
|
|
return PTR_ERR(port);
|
|
|
|
}
|
|
|
|
|
|
|
|
uport = &port->uport;
|
|
|
|
|
|
|
|
if (unlikely(!uport->membase))
|
|
|
|
return -ENXIO;
|
|
|
|
|
|
|
|
if (!port->setup) {
|
2018-09-06 04:11:46 +08:00
|
|
|
ret = qcom_geni_serial_port_setup(uport);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (options)
|
|
|
|
uart_parse_options(options, &baud, &parity, &bits, &flow);
|
|
|
|
|
|
|
|
return uart_set_options(uport, co, baud, parity, bits, flow);
|
|
|
|
}
|
|
|
|
|
2018-05-04 04:14:40 +08:00
|
|
|
static void qcom_geni_serial_earlycon_write(struct console *con,
|
|
|
|
const char *s, unsigned int n)
|
|
|
|
{
|
|
|
|
struct earlycon_device *dev = con->data;
|
|
|
|
|
|
|
|
__qcom_geni_serial_console_write(&dev->port, s, n);
|
|
|
|
}
|
|
|
|
|
2020-05-08 04:08:48 +08:00
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
|
|
static int qcom_geni_serial_earlycon_read(struct console *con,
|
|
|
|
char *s, unsigned int n)
|
|
|
|
{
|
|
|
|
struct earlycon_device *dev = con->data;
|
|
|
|
struct uart_port *uport = &dev->port;
|
|
|
|
int num_read = 0;
|
|
|
|
int ch;
|
|
|
|
|
|
|
|
while (num_read < n) {
|
|
|
|
ch = qcom_geni_serial_get_char(uport);
|
|
|
|
if (ch == NO_POLL_CHAR)
|
|
|
|
break;
|
|
|
|
s[num_read++] = ch;
|
|
|
|
}
|
|
|
|
|
|
|
|
return num_read;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void __init qcom_geni_serial_enable_early_read(struct geni_se *se,
|
|
|
|
struct console *con)
|
|
|
|
{
|
|
|
|
geni_se_setup_s_cmd(se, UART_START_READ, 0);
|
|
|
|
con->read = qcom_geni_serial_earlycon_read;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void qcom_geni_serial_enable_early_read(struct geni_se *se,
|
|
|
|
struct console *con) { }
|
|
|
|
#endif
|
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
static struct qcom_geni_private_data earlycon_private_data;
|
|
|
|
|
2018-05-04 04:14:40 +08:00
|
|
|
static int __init qcom_geni_serial_earlycon_setup(struct earlycon_device *dev,
|
|
|
|
const char *opt)
|
|
|
|
{
|
|
|
|
struct uart_port *uport = &dev->port;
|
|
|
|
u32 tx_trans_cfg;
|
|
|
|
u32 tx_parity_cfg = 0; /* Disable Tx Parity */
|
|
|
|
u32 rx_trans_cfg = 0;
|
|
|
|
u32 rx_parity_cfg = 0; /* Disable Rx Parity */
|
|
|
|
u32 stop_bit_len = 0; /* Default stop bit length - 1 bit */
|
|
|
|
u32 bits_per_char;
|
|
|
|
struct geni_se se;
|
|
|
|
|
|
|
|
if (!uport->membase)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
uport->private_data = &earlycon_private_data;
|
|
|
|
|
2018-05-04 04:14:40 +08:00
|
|
|
memset(&se, 0, sizeof(se));
|
|
|
|
se.base = uport->membase;
|
|
|
|
if (geni_se_read_proto(&se) != GENI_SE_UART)
|
|
|
|
return -ENXIO;
|
|
|
|
/*
|
|
|
|
* Ignore Flow control.
|
|
|
|
* n = 8.
|
|
|
|
*/
|
|
|
|
tx_trans_cfg = UART_CTS_MASK;
|
|
|
|
bits_per_char = BITS_PER_BYTE;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Make an unconditional cancel on the main sequencer to reset
|
|
|
|
* it else we could end up in data loss scenarios.
|
|
|
|
*/
|
|
|
|
qcom_geni_serial_poll_tx_done(uport);
|
|
|
|
qcom_geni_serial_abort_rx(uport);
|
2020-06-27 04:00:33 +08:00
|
|
|
geni_se_config_packing(&se, BITS_PER_BYTE, BYTES_PER_FIFO_WORD,
|
|
|
|
false, true, true);
|
2018-05-04 04:14:40 +08:00
|
|
|
geni_se_init(&se, DEF_FIFO_DEPTH_WORDS / 2, DEF_FIFO_DEPTH_WORDS - 2);
|
|
|
|
geni_se_select_mode(&se, GENI_SE_FIFO);
|
|
|
|
|
2019-01-08 09:58:35 +08:00
|
|
|
writel(tx_trans_cfg, uport->membase + SE_UART_TX_TRANS_CFG);
|
|
|
|
writel(tx_parity_cfg, uport->membase + SE_UART_TX_PARITY_CFG);
|
|
|
|
writel(rx_trans_cfg, uport->membase + SE_UART_RX_TRANS_CFG);
|
|
|
|
writel(rx_parity_cfg, uport->membase + SE_UART_RX_PARITY_CFG);
|
|
|
|
writel(bits_per_char, uport->membase + SE_UART_TX_WORD_LEN);
|
|
|
|
writel(bits_per_char, uport->membase + SE_UART_RX_WORD_LEN);
|
|
|
|
writel(stop_bit_len, uport->membase + SE_UART_TX_STOP_BIT_LEN);
|
2018-05-04 04:14:40 +08:00
|
|
|
|
|
|
|
dev->con->write = qcom_geni_serial_earlycon_write;
|
|
|
|
dev->con->setup = NULL;
|
2020-05-08 04:08:48 +08:00
|
|
|
qcom_geni_serial_enable_early_read(&se, dev->con);
|
|
|
|
|
2018-05-04 04:14:40 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
OF_EARLYCON_DECLARE(qcom_geni, "qcom,geni-debug-uart",
|
|
|
|
qcom_geni_serial_earlycon_setup);
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
static int __init console_register(struct uart_driver *drv)
|
|
|
|
{
|
|
|
|
return uart_register_driver(drv);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void console_unregister(struct uart_driver *drv)
|
|
|
|
{
|
|
|
|
uart_unregister_driver(drv);
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct console cons_ops = {
|
|
|
|
.name = "ttyMSM",
|
|
|
|
.write = qcom_geni_serial_console_write,
|
|
|
|
.device = uart_console_device,
|
|
|
|
.setup = qcom_geni_console_setup,
|
|
|
|
.flags = CON_PRINTBUFFER,
|
|
|
|
.index = -1,
|
|
|
|
.data = &qcom_geni_console_driver,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct uart_driver qcom_geni_console_driver = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.driver_name = "qcom_geni_console",
|
|
|
|
.dev_name = "ttyMSM",
|
|
|
|
.nr = GENI_UART_CONS_PORTS,
|
|
|
|
.cons = &cons_ops,
|
|
|
|
};
|
|
|
|
#else
|
|
|
|
static int console_register(struct uart_driver *drv)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void console_unregister(struct uart_driver *drv)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_SERIAL_QCOM_GENI_CONSOLE */
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
static struct uart_driver qcom_geni_uart_driver = {
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.driver_name = "qcom_geni_uart",
|
|
|
|
.dev_name = "ttyHS",
|
|
|
|
.nr = GENI_UART_PORTS,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void qcom_geni_serial_pm(struct uart_port *uport,
|
2018-03-15 07:58:49 +08:00
|
|
|
unsigned int new_state, unsigned int old_state)
|
|
|
|
{
|
|
|
|
struct qcom_geni_serial_port *port = to_dev_port(uport, uport);
|
|
|
|
|
2018-09-06 04:11:46 +08:00
|
|
|
/* If we've never been called, treat it as off */
|
|
|
|
if (old_state == UART_PM_STATE_UNDEFINED)
|
|
|
|
old_state = UART_PM_STATE_OFF;
|
|
|
|
|
2020-06-23 18:38:53 +08:00
|
|
|
if (new_state == UART_PM_STATE_ON && old_state == UART_PM_STATE_OFF) {
|
|
|
|
geni_icc_enable(&port->se);
|
2018-03-15 07:58:49 +08:00
|
|
|
geni_se_resources_on(&port->se);
|
2020-06-23 18:38:53 +08:00
|
|
|
} else if (new_state == UART_PM_STATE_OFF &&
|
|
|
|
old_state == UART_PM_STATE_ON) {
|
2018-03-15 07:58:49 +08:00
|
|
|
geni_se_resources_off(&port->se);
|
2020-06-23 18:38:53 +08:00
|
|
|
geni_icc_disable(&port->se);
|
|
|
|
}
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct uart_ops qcom_geni_console_pops = {
|
|
|
|
.tx_empty = qcom_geni_serial_tx_empty,
|
|
|
|
.stop_tx = qcom_geni_serial_stop_tx,
|
|
|
|
.start_tx = qcom_geni_serial_start_tx,
|
|
|
|
.stop_rx = qcom_geni_serial_stop_rx,
|
|
|
|
.set_termios = qcom_geni_serial_set_termios,
|
|
|
|
.startup = qcom_geni_serial_startup,
|
|
|
|
.request_port = qcom_geni_serial_request_port,
|
|
|
|
.config_port = qcom_geni_serial_config_port,
|
|
|
|
.shutdown = qcom_geni_serial_shutdown,
|
|
|
|
.type = qcom_geni_serial_get_type,
|
2018-07-14 06:17:35 +08:00
|
|
|
.set_mctrl = qcom_geni_serial_set_mctrl,
|
|
|
|
.get_mctrl = qcom_geni_serial_get_mctrl,
|
2018-03-15 07:58:49 +08:00
|
|
|
#ifdef CONFIG_CONSOLE_POLL
|
|
|
|
.poll_get_char = qcom_geni_serial_get_char,
|
|
|
|
.poll_put_char = qcom_geni_serial_poll_put_char,
|
|
|
|
#endif
|
2018-07-14 06:17:35 +08:00
|
|
|
.pm = qcom_geni_serial_pm,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct uart_ops qcom_geni_uart_pops = {
|
|
|
|
.tx_empty = qcom_geni_serial_tx_empty,
|
|
|
|
.stop_tx = qcom_geni_serial_stop_tx,
|
|
|
|
.start_tx = qcom_geni_serial_start_tx,
|
|
|
|
.stop_rx = qcom_geni_serial_stop_rx,
|
|
|
|
.set_termios = qcom_geni_serial_set_termios,
|
|
|
|
.startup = qcom_geni_serial_startup,
|
|
|
|
.request_port = qcom_geni_serial_request_port,
|
|
|
|
.config_port = qcom_geni_serial_config_port,
|
|
|
|
.shutdown = qcom_geni_serial_shutdown,
|
|
|
|
.type = qcom_geni_serial_get_type,
|
|
|
|
.set_mctrl = qcom_geni_serial_set_mctrl,
|
|
|
|
.get_mctrl = qcom_geni_serial_get_mctrl,
|
|
|
|
.pm = qcom_geni_serial_pm,
|
2018-03-15 07:58:49 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int qcom_geni_serial_probe(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
int line = -1;
|
|
|
|
struct qcom_geni_serial_port *port;
|
|
|
|
struct uart_port *uport;
|
|
|
|
struct resource *res;
|
2018-04-07 08:49:00 +08:00
|
|
|
int irq;
|
2018-07-14 06:17:35 +08:00
|
|
|
bool console = false;
|
|
|
|
struct uart_driver *drv;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
if (of_device_is_compatible(pdev->dev.of_node, "qcom,geni-debug-uart"))
|
|
|
|
console = true;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2018-08-24 05:30:27 +08:00
|
|
|
if (console) {
|
|
|
|
drv = &qcom_geni_console_driver;
|
|
|
|
line = of_alias_get_id(pdev->dev.of_node, "serial");
|
|
|
|
} else {
|
|
|
|
drv = &qcom_geni_uart_driver;
|
|
|
|
line = of_alias_get_id(pdev->dev.of_node, "hsuart");
|
2018-07-14 06:17:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
port = get_port_from_line(line, console);
|
2018-03-15 07:58:49 +08:00
|
|
|
if (IS_ERR(port)) {
|
2018-05-04 04:14:34 +08:00
|
|
|
dev_err(&pdev->dev, "Invalid line %d\n", line);
|
|
|
|
return PTR_ERR(port);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
uport = &port->uport;
|
|
|
|
/* Don't allow 2 drivers to access the same port */
|
|
|
|
if (uport->private_data)
|
|
|
|
return -ENODEV;
|
|
|
|
|
|
|
|
uport->dev = &pdev->dev;
|
|
|
|
port->se.dev = &pdev->dev;
|
|
|
|
port->se.wrapper = dev_get_drvdata(pdev->dev.parent);
|
|
|
|
port->se.clk = devm_clk_get(&pdev->dev, "se");
|
|
|
|
if (IS_ERR(port->se.clk)) {
|
|
|
|
ret = PTR_ERR(port->se.clk);
|
|
|
|
dev_err(&pdev->dev, "Err getting SE Core clk %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
2018-03-22 10:25:07 +08:00
|
|
|
if (!res)
|
|
|
|
return -EINVAL;
|
2018-03-15 07:58:49 +08:00
|
|
|
uport->mapbase = res->start;
|
|
|
|
|
|
|
|
port->tx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
|
|
|
|
port->rx_fifo_depth = DEF_FIFO_DEPTH_WORDS;
|
|
|
|
port->tx_fifo_width = DEF_FIFO_WIDTH_BITS;
|
|
|
|
|
2020-03-06 14:47:07 +08:00
|
|
|
if (!console) {
|
|
|
|
port->rx_fifo = devm_kcalloc(uport->dev,
|
|
|
|
port->rx_fifo_depth, sizeof(u32), GFP_KERNEL);
|
|
|
|
if (!port->rx_fifo)
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
2020-06-23 18:38:53 +08:00
|
|
|
ret = geni_icc_get(&port->se, NULL);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
port->se.icc_paths[GENI_TO_CORE].avg_bw = GENI_DEFAULT_BW;
|
|
|
|
port->se.icc_paths[CPU_TO_GENI].avg_bw = GENI_DEFAULT_BW;
|
|
|
|
|
|
|
|
/* Set BW for register access */
|
|
|
|
ret = geni_icc_set_bw(&port->se);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2020-01-06 22:45:04 +08:00
|
|
|
port->name = devm_kasprintf(uport->dev, GFP_KERNEL,
|
|
|
|
"qcom_geni_serial_%s%d",
|
|
|
|
uart_console(uport) ? "console" : "uart", uport->line);
|
|
|
|
if (!port->name)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2018-04-07 08:49:00 +08:00
|
|
|
irq = platform_get_irq(pdev, 0);
|
2019-07-31 02:15:44 +08:00
|
|
|
if (irq < 0)
|
2018-04-07 08:49:00 +08:00
|
|
|
return irq;
|
|
|
|
uport->irq = irq;
|
2019-12-13 08:06:34 +08:00
|
|
|
uport->has_sysrq = IS_ENABLED(CONFIG_SERIAL_QCOM_GENI_CONSOLE);
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-01-06 22:45:04 +08:00
|
|
|
if (!console)
|
|
|
|
port->wakeup_irq = platform_get_irq_optional(pdev, 1);
|
|
|
|
|
2020-03-04 19:22:03 +08:00
|
|
|
if (of_property_read_bool(pdev->dev.of_node, "rx-tx-swap"))
|
|
|
|
port->rx_tx_swap = true;
|
|
|
|
|
|
|
|
if (of_property_read_bool(pdev->dev.of_node, "cts-rts-swap"))
|
|
|
|
port->cts_rts_swap = true;
|
|
|
|
|
2020-06-15 20:02:39 +08:00
|
|
|
port->se.opp_table = dev_pm_opp_set_clkname(&pdev->dev, "se");
|
|
|
|
if (IS_ERR(port->se.opp_table))
|
|
|
|
return PTR_ERR(port->se.opp_table);
|
|
|
|
/* OPP table is optional */
|
|
|
|
ret = dev_pm_opp_of_add_table(&pdev->dev);
|
2020-08-28 14:07:52 +08:00
|
|
|
if (ret && ret != -ENODEV) {
|
2020-06-15 20:02:39 +08:00
|
|
|
dev_err(&pdev->dev, "invalid OPP table in device tree\n");
|
2020-08-28 14:07:52 +08:00
|
|
|
goto put_clkname;
|
2020-06-15 20:02:39 +08:00
|
|
|
}
|
|
|
|
|
2020-06-27 04:00:32 +08:00
|
|
|
port->private_data.drv = drv;
|
|
|
|
uport->private_data = &port->private_data;
|
2020-01-06 22:45:04 +08:00
|
|
|
platform_set_drvdata(pdev, port);
|
|
|
|
port->handle_rx = console ? handle_rx_console : handle_rx_uart;
|
|
|
|
|
|
|
|
ret = uart_add_one_port(drv, uport);
|
|
|
|
if (ret)
|
2020-06-15 20:02:39 +08:00
|
|
|
goto err;
|
2020-01-06 22:45:04 +08:00
|
|
|
|
2019-10-10 17:46:03 +08:00
|
|
|
irq_set_status_flags(uport->irq, IRQ_NOAUTOEN);
|
|
|
|
ret = devm_request_irq(uport->dev, uport->irq, qcom_geni_serial_isr,
|
|
|
|
IRQF_TRIGGER_HIGH, port->name, uport);
|
|
|
|
if (ret) {
|
|
|
|
dev_err(uport->dev, "Failed to get IRQ ret %d\n", ret);
|
2020-01-06 22:45:04 +08:00
|
|
|
uart_remove_one_port(drv, uport);
|
2020-06-15 20:02:39 +08:00
|
|
|
goto err;
|
2019-10-10 17:46:03 +08:00
|
|
|
}
|
|
|
|
|
2020-01-06 22:45:04 +08:00
|
|
|
/*
|
|
|
|
* Set pm_runtime status as ACTIVE so that wakeup_irq gets
|
|
|
|
* enabled/disabled from dev_pm_arm_wake_irq during system
|
|
|
|
* suspend/resume respectively.
|
|
|
|
*/
|
|
|
|
pm_runtime_set_active(&pdev->dev);
|
|
|
|
|
|
|
|
if (port->wakeup_irq > 0) {
|
|
|
|
device_init_wakeup(&pdev->dev, true);
|
|
|
|
ret = dev_pm_set_dedicated_wake_irq(&pdev->dev,
|
|
|
|
port->wakeup_irq);
|
|
|
|
if (ret) {
|
|
|
|
device_init_wakeup(&pdev->dev, false);
|
|
|
|
uart_remove_one_port(drv, uport);
|
2020-06-15 20:02:39 +08:00
|
|
|
goto err;
|
2019-10-10 17:46:43 +08:00
|
|
|
}
|
|
|
|
}
|
2020-01-06 22:45:04 +08:00
|
|
|
|
|
|
|
return 0;
|
2020-06-15 20:02:39 +08:00
|
|
|
err:
|
2020-08-28 14:07:52 +08:00
|
|
|
dev_pm_opp_of_remove_table(&pdev->dev);
|
|
|
|
put_clkname:
|
2020-06-15 20:02:39 +08:00
|
|
|
dev_pm_opp_put_clkname(port->se.opp_table);
|
|
|
|
return ret;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int qcom_geni_serial_remove(struct platform_device *pdev)
|
|
|
|
{
|
|
|
|
struct qcom_geni_serial_port *port = platform_get_drvdata(pdev);
|
2020-06-27 04:00:32 +08:00
|
|
|
struct uart_driver *drv = port->private_data.drv;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-08-28 14:07:52 +08:00
|
|
|
dev_pm_opp_of_remove_table(&pdev->dev);
|
2020-06-15 20:02:39 +08:00
|
|
|
dev_pm_opp_put_clkname(port->se.opp_table);
|
2020-01-06 22:45:04 +08:00
|
|
|
dev_pm_clear_wake_irq(&pdev->dev);
|
|
|
|
device_init_wakeup(&pdev->dev, false);
|
2018-03-15 07:58:49 +08:00
|
|
|
uart_remove_one_port(drv, &port->uport);
|
2020-01-06 22:45:04 +08:00
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-10-16 22:11:00 +08:00
|
|
|
static int __maybe_unused qcom_geni_serial_sys_suspend(struct device *dev)
|
2018-03-15 07:58:49 +08:00
|
|
|
{
|
2018-04-19 22:06:23 +08:00
|
|
|
struct qcom_geni_serial_port *port = dev_get_drvdata(dev);
|
2018-03-15 07:58:49 +08:00
|
|
|
struct uart_port *uport = &port->uport;
|
2020-06-27 04:00:32 +08:00
|
|
|
struct qcom_geni_private_data *private_data = uport->private_data;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-07-14 13:31:49 +08:00
|
|
|
/*
|
|
|
|
* This is done so we can hit the lowest possible state in suspend
|
|
|
|
* even with no_console_suspend
|
|
|
|
*/
|
|
|
|
if (uart_console(uport)) {
|
|
|
|
geni_icc_set_tag(&port->se, 0x3);
|
|
|
|
geni_icc_set_bw(&port->se);
|
|
|
|
}
|
2020-06-27 04:00:32 +08:00
|
|
|
return uart_suspend_port(private_data->drv, uport);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
2018-10-16 22:11:00 +08:00
|
|
|
static int __maybe_unused qcom_geni_serial_sys_resume(struct device *dev)
|
2018-03-15 07:58:49 +08:00
|
|
|
{
|
2020-07-14 13:31:49 +08:00
|
|
|
int ret;
|
2018-04-19 22:06:23 +08:00
|
|
|
struct qcom_geni_serial_port *port = dev_get_drvdata(dev);
|
2018-03-15 07:58:49 +08:00
|
|
|
struct uart_port *uport = &port->uport;
|
2020-06-27 04:00:32 +08:00
|
|
|
struct qcom_geni_private_data *private_data = uport->private_data;
|
2018-03-15 07:58:49 +08:00
|
|
|
|
2020-07-14 13:31:49 +08:00
|
|
|
ret = uart_resume_port(private_data->drv, uport);
|
|
|
|
if (uart_console(uport)) {
|
|
|
|
geni_icc_set_tag(&port->se, 0x7);
|
|
|
|
geni_icc_set_bw(&port->se);
|
|
|
|
}
|
|
|
|
return ret;
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct dev_pm_ops qcom_geni_serial_pm_ops = {
|
2018-10-16 22:11:00 +08:00
|
|
|
SET_SYSTEM_SLEEP_PM_OPS(qcom_geni_serial_sys_suspend,
|
|
|
|
qcom_geni_serial_sys_resume)
|
2018-03-15 07:58:49 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct of_device_id qcom_geni_serial_match_table[] = {
|
|
|
|
{ .compatible = "qcom,geni-debug-uart", },
|
2018-07-14 06:17:35 +08:00
|
|
|
{ .compatible = "qcom,geni-uart", },
|
2018-03-15 07:58:49 +08:00
|
|
|
{}
|
|
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, qcom_geni_serial_match_table);
|
|
|
|
|
|
|
|
static struct platform_driver qcom_geni_serial_platform_driver = {
|
|
|
|
.remove = qcom_geni_serial_remove,
|
|
|
|
.probe = qcom_geni_serial_probe,
|
|
|
|
.driver = {
|
|
|
|
.name = "qcom_geni_serial",
|
|
|
|
.of_match_table = qcom_geni_serial_match_table,
|
|
|
|
.pm = &qcom_geni_serial_pm_ops,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init qcom_geni_serial_init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = console_register(&qcom_geni_console_driver);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
2018-07-14 06:17:35 +08:00
|
|
|
ret = uart_register_driver(&qcom_geni_uart_driver);
|
|
|
|
if (ret) {
|
|
|
|
console_unregister(&qcom_geni_console_driver);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-03-15 07:58:49 +08:00
|
|
|
ret = platform_driver_register(&qcom_geni_serial_platform_driver);
|
2018-07-14 06:17:35 +08:00
|
|
|
if (ret) {
|
2018-03-15 07:58:49 +08:00
|
|
|
console_unregister(&qcom_geni_console_driver);
|
2018-07-14 06:17:35 +08:00
|
|
|
uart_unregister_driver(&qcom_geni_uart_driver);
|
|
|
|
}
|
2018-03-15 07:58:49 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
module_init(qcom_geni_serial_init);
|
|
|
|
|
|
|
|
static void __exit qcom_geni_serial_exit(void)
|
|
|
|
{
|
|
|
|
platform_driver_unregister(&qcom_geni_serial_platform_driver);
|
|
|
|
console_unregister(&qcom_geni_console_driver);
|
2018-07-14 06:17:35 +08:00
|
|
|
uart_unregister_driver(&qcom_geni_uart_driver);
|
2018-03-15 07:58:49 +08:00
|
|
|
}
|
|
|
|
module_exit(qcom_geni_serial_exit);
|
|
|
|
|
|
|
|
MODULE_DESCRIPTION("Serial driver for GENI based QUP cores");
|
|
|
|
MODULE_LICENSE("GPL v2");
|