// SPDX-License-Identifier: GPL-2.0+
/*
* PIC32 Integrated Serial Driver.
*
* Copyright (C) 2015 Microchip Technology, Inc.
*
* Authors:
* Sorin-Andrei Pistirica <andrei.pistirica@microchip.com>
*/
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/of_gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/console.h>
#include <linux/clk.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/serial_core.h>
#include <linux/delay.h>
#include <asm/mach-pic32/pic32.h>
#include "pic32_uart.h"
/* UART name and device definitions */
#define PIC32_DEV_NAME "pic32-uart"
#define PIC32_MAX_UARTS 6
#define PIC32_SDEV_NAME "ttyPIC"
/* pic32_sport pointer for console use */
static struct pic32_sport *pic32_sports[PIC32_MAX_UARTS];
static inline void pic32_wait_deplete_txbuf(struct pic32_sport *sport)
{
/* wait for tx empty, otherwise chars will be lost or corrupted */
while (!(pic32_uart_readl(sport, PIC32_UART_STA) & PIC32_UART_STA_TRMT))
udelay(1);
}
static inline int pic32_enable_clock(struct pic32_sport *sport)
{
int ret = clk_prepare_enable(sport->clk);
if (ret)
return ret;
sport->ref_clk++;
return 0;
}
static inline void pic32_disable_clock(struct pic32_sport *sport)
{
sport->ref_clk--;
clk_disable_unprepare(sport->clk);
}
/* serial core request to check if uart tx buffer is empty */
static unsigned int pic32_uart_tx_empty(struct uart_port *port)
{
struct pic32_sport *sport = to_pic32_sport(port);
u32 val = pic32_uart_readl(sport, PIC32_UART_STA);
return (val & PIC32_UART_STA_TRMT) ? 1 : 0;
}
/* serial core request to set UART outputs */
static void pic32_uart_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
struct pic32_sport *sport = to_pic32_sport(port);
/* set loopback mode */
if (mctrl & TIOCM_LOOP)
pic32_uart_writel(sport, PIC32_SET(PIC32_UART_MODE),
PIC32_UART_MODE_LPBK);
else
pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_MODE),
PIC32_UART_MODE_LPBK);
}
/* get the state of CTS input pin for this port */
static unsigned int get_cts_state(struct pic32_sport *sport)
{
/* read and invert UxCTS */
if (gpio_is_valid(sport->cts_gpio))
return !gpio_get_value(sport->cts_gpio);
return 1;
}
/* serial core request to return the state of misc UART input pins */
static unsigned int pic32_uart_get_mctrl(struct uart_port *port)
{
struct pic32_sport *sport = to_pic32_sport(port);
unsigned int mctrl = 0;
if (!sport->hw_flow_ctrl)
mctrl |= TIOCM_CTS;
else if (get_cts_state(sport))
mctrl |= TIOCM_CTS;
/* DSR and CD are not supported in PIC32, so return 1
* RI is not supported in PIC32, so return 0
*/
mctrl |= TIOCM_CD;
mctrl |= TIOCM_DSR;
return mctrl;
}
/* stop tx and start tx are not called in pairs, therefore a flag indicates
* the status of irq to control the irq-depth.
*/
static inline void pic32_uart_irqtxen(struct pic32_sport *sport, u8 en)
{
if (en && !tx_irq_enabled(sport)) {
enable_irq(sport->irq_tx);
tx_irq_enabled(sport) = 1;
} else if (!en && tx_irq_enabled(sport)) {
/* use disable_irq_nosync() and not disable_irq() to avoid self
* imposed deadlock by not waiting for irq handler to end,
* since this callback is called from interrupt context.
*/
disable_irq_nosync(sport->irq_tx);
tx_irq_enabled(sport) = 0;
}
}
/* serial core request to disable tx ASAP (used for flow control) */
static void pic32_uart_stop_tx(struct uart_port *port)
{
struct pic32_sport *sport = to_pic32_sport(port);
if (!(pic32_uart_readl(sport, PIC32_UART_MODE) & PIC32_UART_MODE_ON))
return;
if (!(pic32_uart_readl(sport, PIC32_UART_STA) & PIC32_UART_STA_UTXEN))
return;
/* wait for tx empty */
pic32_wait_deplete_txbuf(sport);
pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
PIC32_UART_STA_UTXEN);
pic32_uart_irqtxen(sport, 0);
}
/* serial core request to (re)enable tx */
static void pic32_uart_start_tx(struct uart_port *port)
{
struct pic32_sport *sport = to_pic32_sport(port);
pic32_uart_irqtxen(sport, 1);
pic32_uart_writel(sport, PIC32_SET(PIC32_UART_STA),
PIC32_UART_STA_UTXEN);
}
/* serial core request to stop rx, called before port shutdown */
static void pic32_uart_stop_rx(struct uart_port *port)
{
struct pic32_sport *sport = to_pic32_sport(port);
/* disable rx interrupts */
disable_irq(sport->irq_rx);
/* receiver Enable bit OFF */
pic32_uart_writel(sport, PIC32_CLR(PIC32_UART_STA),
PIC32_UART_STA_URXEN);
}
/* serial core request to start/stop emitting break char */
static void pic32_uart_break_ctl(struct uart_port *port, int ctl)
{
str