tegrakernel/kernel/nvidia/drivers/tty/serial/tegra-combined-uart-early.c

89 lines
2.6 KiB
C

/*
* Copyright (c) 2017-2018, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <asm/io.h>
#include <linux/console.h>
#include <linux/serial_core.h>
#define NUM_BYTES_FIELD_BIT 24
#define FLUSH_BIT 26
#define INTR_TRIGGER_BIT 31
static u32 update_and_send_mbox(u8 __iomem *addr, u32 mbox_val, char c)
{
int bytes = bytes = (mbox_val >> NUM_BYTES_FIELD_BIT) & 0x3;
mbox_val |= BIT(INTR_TRIGGER_BIT);
mbox_val |= c << (bytes * 8);
bytes++;
mbox_val = (mbox_val & ~(3 << NUM_BYTES_FIELD_BIT)) |
(bytes << NUM_BYTES_FIELD_BIT);
if (bytes == 3) {
/* Send current packet to SPE */
while (readl(addr) & BIT(INTR_TRIGGER_BIT))
cpu_relax();
writel(mbox_val, addr);
mbox_val = BIT(INTR_TRIGGER_BIT);
}
return mbox_val;
}
/*
* This function splits the string to be printed (const char *s) into multiple
* packets. Each packet contains a max of 3 characters. Packets are sent to the
* SPE-based combined UART server for printing. Communication with SPE is done
* through mailbox registers which can generate interrupts for SPE.
*/
static void __init early_tcu_write(struct console *console,
const char *s, unsigned int count)
{
struct earlycon_device *device = console->data;
u8 __iomem *addr = device->port.membase;
u32 mbox_val = BIT(INTR_TRIGGER_BIT);
unsigned int i;
/* Loop for processing each 3 char packet */
for (i = 0; i < count; i++) {
if (s[i] == '\n')
mbox_val = update_and_send_mbox(addr, mbox_val, '\r');
mbox_val = update_and_send_mbox(addr, mbox_val, s[i]);
}
if ((mbox_val >> NUM_BYTES_FIELD_BIT) & 0x3) {
while (readl(addr) & BIT(INTR_TRIGGER_BIT))
cpu_relax();
writel(mbox_val, addr);
}
}
int __init early_tegra_combined_uart_setup(struct earlycon_device *device,
const char *options)
{
if (!(device->port.membase))
return -ENODEV;
device->con->write = early_tcu_write;
#ifdef CONFIG_SERIAL_LOGLEVEL_PRINT
device->con->flags |= CON_FORCE_LEVEL;
#endif
return 0;
}
EARLYCON_DECLARE(tegra_comb_uart, early_tegra_combined_uart_setup);