mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
nvidia-oot: resurrect tegra_hv_comm TTY driver
Port the tegra_hv_comm driver to the OOT kernel. This driver allows an IVC channel to be used as an emulated serial device. Make the following changes to simplify maintaining compatibility with different kernel versions: - Use a generic port type instead of defining a custom one. - Use existing conftest to determine whether platform_device.remove returns void. - Add a conftest to determine whether uart_state.xmit exists. - Use tty_port.xmit_fifo in kernels where uart_state.xmit has been removed. Jira HYP-20929 Change-Id: I1bc218f591d6d741fc39ae5392a9ea95bdc3f117 Signed-off-by: Daniel Kudrow <dkudrow@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3340420 (cherry picked from commit a67e7fd330be5d6c55e35c0ce810ff5124378ae7 in embedded/7.0.3.0) Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3301386 Reviewed-by: Bitan Biswas <bbiswas@nvidia.com> GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
committed by
Jon Hunter
parent
a81de1a2c6
commit
79cf6380ea
@@ -157,6 +157,7 @@ kernel_module(
|
|||||||
"drivers/spi/spi-tegra210-quad.ko",
|
"drivers/spi/spi-tegra210-quad.ko",
|
||||||
"drivers/thermal/max77851_thermal.ko",
|
"drivers/thermal/max77851_thermal.ko",
|
||||||
"drivers/tty/serial/wch_35x/wch.ko",
|
"drivers/tty/serial/wch_35x/wch.ko",
|
||||||
|
"drivers/tty/serial/tegra_hv_comm.ko",
|
||||||
"drivers/usb/typec/fusb301.ko",
|
"drivers/usb/typec/fusb301.ko",
|
||||||
"drivers/video/tegra/camera/tegra_camera_platform.ko",
|
"drivers/video/tegra/camera/tegra_camera_platform.ko",
|
||||||
"drivers/video/tegra/dc/bridge/maxim_gmsl_dp_serializer.ko",
|
"drivers/video/tegra/dc/bridge/maxim_gmsl_dp_serializer.ko",
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
obj-m += wch_35x/
|
obj-m += wch_35x/
|
||||||
|
obj-m += tegra_hv_comm.o
|
||||||
|
|||||||
659
drivers/tty/serial/tegra_hv_comm.c
Normal file
659
drivers/tty/serial/tegra_hv_comm.c
Normal file
@@ -0,0 +1,659 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
// SPDX-FileCopyrightText: Copyright (c) 2014-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||||
|
/*
|
||||||
|
* tegra_hv_comm.c: TTY over Tegra HV
|
||||||
|
*
|
||||||
|
* Very loosely based on altera_jtaguart.c
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#undef DEBUG
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/init.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include <linux/tty.h>
|
||||||
|
#include <linux/tty_flip.h>
|
||||||
|
#include <linux/serial.h>
|
||||||
|
#include <linux/serial_core.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/io.h>
|
||||||
|
|
||||||
|
#include <nvidia/conftest.h>
|
||||||
|
|
||||||
|
#define DRV_NAME "tegra_hv_comm"
|
||||||
|
|
||||||
|
/* The port type is not important to userspace so use PORT_GENERIC. */
|
||||||
|
#ifndef PORT_GENERIC
|
||||||
|
#define PORT_GENERIC (-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <soc/tegra/virt/hv-ivc.h>
|
||||||
|
|
||||||
|
/* frame format is
|
||||||
|
* 0000: <size>
|
||||||
|
* 0004: <flags>
|
||||||
|
* 0008: data
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define HDR_SIZE 8
|
||||||
|
|
||||||
|
struct tegra_hv_comm {
|
||||||
|
struct uart_port port;
|
||||||
|
struct platform_device *pdev;
|
||||||
|
struct tegra_hv_ivc_cookie *ivck;
|
||||||
|
unsigned int sigs;
|
||||||
|
unsigned long imr;
|
||||||
|
unsigned int tx_en:1;
|
||||||
|
struct timer_list tx_timer;
|
||||||
|
unsigned int rx_en:1;
|
||||||
|
void *rx_frame;
|
||||||
|
u8 *rx_buf; /* always points to rx_frame + HDR_SIZE */
|
||||||
|
void *tx_frame;
|
||||||
|
u8 *tx_buf; /* always points to tx_frame + HDR_SIZE */
|
||||||
|
void *buf;
|
||||||
|
int rx_in, rx_cnt;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define to_tegra_hv_comm(_x) \
|
||||||
|
container_of(_x, struct tegra_hv_comm, port)
|
||||||
|
|
||||||
|
static unsigned int tegra_hv_comm_tx_empty(struct uart_port *port)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
return tegra_hv_ivc_tx_empty(pp->ivck) ? TIOCSER_TEMT : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned int tegra_hv_comm_get_mctrl(struct uart_port *port)
|
||||||
|
{
|
||||||
|
/* fake it */
|
||||||
|
return TIOCM_CAR | TIOCM_DSR | TIOCM_CTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_set_mctrl(struct uart_port *port, unsigned int sigs)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
|
||||||
|
/* nothing */
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int xmit_active(struct tegra_hv_comm *pp)
|
||||||
|
{
|
||||||
|
struct uart_port *port = &pp->port;
|
||||||
|
#ifdef NV_UART_STATE_HAS_XMIT /* Linux v6.10 */
|
||||||
|
struct circ_buf *xmit = &port->state->xmit;
|
||||||
|
|
||||||
|
return port->x_char != 0 || uart_circ_chars_pending(xmit) > 0;
|
||||||
|
#else
|
||||||
|
struct tty_port *tport = &port->state->port;
|
||||||
|
|
||||||
|
return port->x_char != 0 || !kfifo_is_empty(&tport->xmit_fifo);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void xmit_timer_setup(struct tegra_hv_comm *pp, int in_timer)
|
||||||
|
{
|
||||||
|
if (xmit_active(pp)) {
|
||||||
|
if (!pp->tx_en) {
|
||||||
|
pp->tx_en = 1;
|
||||||
|
mod_timer(&pp->tx_timer, jiffies + HZ / 8);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pp->tx_en) {
|
||||||
|
pp->tx_en = 0;
|
||||||
|
if (!in_timer)
|
||||||
|
del_timer(&pp->tx_timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_tx_chars(struct tegra_hv_comm *pp)
|
||||||
|
{
|
||||||
|
struct uart_port *port = &pp->port;
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
#ifdef NV_UART_STATE_HAS_XMIT
|
||||||
|
struct circ_buf *xmit = &port->state->xmit;
|
||||||
|
#else
|
||||||
|
struct tty_port *tport = &port->state->port;
|
||||||
|
#endif
|
||||||
|
unsigned int pending, count;
|
||||||
|
char *s;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
if (!tegra_hv_ivc_can_write(pp->ivck))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (port->x_char) {
|
||||||
|
/* Send special char - probably flow control */
|
||||||
|
|
||||||
|
((u32 *)pp->tx_frame)[0] = 1; /* count */
|
||||||
|
((u32 *)pp->tx_frame)[1] = 0; /* flags */
|
||||||
|
s = pp->tx_frame + HDR_SIZE;
|
||||||
|
*s++ = port->x_char;
|
||||||
|
|
||||||
|
count = (void *)s - pp->tx_frame + HDR_SIZE;
|
||||||
|
memset(pp->tx_frame + count, 0, pp->ivck->frame_size - count);
|
||||||
|
ret = tegra_hv_ivc_write(pp->ivck, pp->tx_frame, count);
|
||||||
|
if (ret != count) {
|
||||||
|
/* error */
|
||||||
|
dev_err(dev, "%s: failed to write x_char\n", __func__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
port->x_char = 0;
|
||||||
|
port->icount.tx++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef NV_UART_STATE_HAS_XMIT
|
||||||
|
pending = uart_circ_chars_pending(xmit);
|
||||||
|
#else
|
||||||
|
pending = kfifo_len(&tport->xmit_fifo);
|
||||||
|
#endif
|
||||||
|
if (pending > 0) {
|
||||||
|
count = pp->ivck->frame_size - HDR_SIZE;
|
||||||
|
if (count > pending)
|
||||||
|
count = pending;
|
||||||
|
if (count > 0) {
|
||||||
|
pending -= count;
|
||||||
|
|
||||||
|
((u32 *)pp->tx_frame)[0] = count; /* count */
|
||||||
|
((u32 *)pp->tx_frame)[1] = 0; /* flags */
|
||||||
|
s = pp->tx_frame + HDR_SIZE;
|
||||||
|
#ifdef NV_UART_STATE_HAS_XMIT
|
||||||
|
while (count-- > 0) {
|
||||||
|
*s++ = xmit->buf[xmit->tail];
|
||||||
|
xmit->tail = (xmit->tail + 1) &
|
||||||
|
(UART_XMIT_SIZE - 1);
|
||||||
|
port->icount.tx++;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
count = kfifo_out(&tport->xmit_fifo, s, count);
|
||||||
|
s += count;
|
||||||
|
port->icount.tx += count;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* count is now from start of frame */
|
||||||
|
count = (void *)s - pp->tx_frame;
|
||||||
|
if (count > 0)
|
||||||
|
memset(pp->tx_frame + count, 0,
|
||||||
|
pp->ivck->frame_size - count);
|
||||||
|
|
||||||
|
ret = tegra_hv_ivc_write(pp->ivck, pp->tx_frame, count);
|
||||||
|
if (ret != count) {
|
||||||
|
/* error */
|
||||||
|
dev_err(dev, "%s: failed to write normal data\n",
|
||||||
|
__func__);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pending < WAKEUP_CHARS)
|
||||||
|
uart_write_wakeup(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_tx_timer_expired(struct timer_list *timer)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = container_of(timer, struct tegra_hv_comm, tx_timer);
|
||||||
|
struct uart_port *port = &pp->port;
|
||||||
|
|
||||||
|
spin_lock(&port->lock);
|
||||||
|
tegra_hv_comm_tx_chars(pp);
|
||||||
|
xmit_timer_setup(pp, 1);
|
||||||
|
spin_unlock(&port->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_start_tx(struct uart_port *port)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
tegra_hv_comm_tx_chars(pp);
|
||||||
|
xmit_timer_setup(pp, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_stop_tx(struct uart_port *port)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
if (pp->tx_en) {
|
||||||
|
pp->tx_en = 0;
|
||||||
|
del_timer_sync(&pp->tx_timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_stop_rx(struct uart_port *port)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
pp->rx_en = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_break_ctl(struct uart_port *port, int break_state)
|
||||||
|
{
|
||||||
|
/* nothing */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_enable_ms(struct uart_port *port)
|
||||||
|
{
|
||||||
|
/* nothing */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_set_termios(struct uart_port *port,
|
||||||
|
struct ktermios *termios,
|
||||||
|
const struct ktermios *old)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
/* Just copy the old termios settings back */
|
||||||
|
if (old)
|
||||||
|
tty_termios_copy_hw(termios, old);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_rx_chars(struct tegra_hv_comm *pp)
|
||||||
|
{
|
||||||
|
struct uart_port *port = &pp->port;
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
unsigned char ch, flag;
|
||||||
|
u32 cnt, flags;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
/* no more data, get more */
|
||||||
|
while (pp->rx_in >= pp->rx_cnt &&
|
||||||
|
tegra_hv_ivc_can_read(pp->ivck)) {
|
||||||
|
|
||||||
|
ret = tegra_hv_ivc_read(pp->ivck, pp->rx_frame,
|
||||||
|
pp->ivck->frame_size);
|
||||||
|
|
||||||
|
/* error */
|
||||||
|
if (ret <= 0) {
|
||||||
|
dev_err(dev, "%s: ivc_read failed (ret=%d)\n",
|
||||||
|
__func__, ret);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* not enough data */
|
||||||
|
if (ret < HDR_SIZE) {
|
||||||
|
dev_err(dev, "%s: ivc_read short read (ret=%d)\n",
|
||||||
|
__func__, ret);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = ((u32 *)pp->rx_frame)[0];
|
||||||
|
flags = ((u32 *)pp->rx_frame)[1];
|
||||||
|
|
||||||
|
/* validate */
|
||||||
|
if (cnt > pp->ivck->frame_size - HDR_SIZE) {
|
||||||
|
dev_err(dev, "%s: ivc_read bogus frame (cnt=%d)\n",
|
||||||
|
__func__, cnt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pp->rx_cnt = cnt;
|
||||||
|
pp->rx_in = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pp->rx_in >= pp->rx_cnt)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* next character */
|
||||||
|
ch = pp->rx_buf[pp->rx_in++];
|
||||||
|
|
||||||
|
flag = TTY_NORMAL;
|
||||||
|
port->icount.rx++;
|
||||||
|
|
||||||
|
if (uart_handle_sysrq_char(port, ch))
|
||||||
|
continue;
|
||||||
|
uart_insert_char(port, 0, 0, ch, flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
tty_flip_buffer_push(&port->state->port);
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t tegra_hv_comm_interrupt(int irq, void *data)
|
||||||
|
{
|
||||||
|
struct uart_port *port = data;
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
spin_lock(&port->lock);
|
||||||
|
|
||||||
|
/* until this function returns 0, the channel is unusable */
|
||||||
|
if (tegra_hv_ivc_channel_notified(pp->ivck) == 0) {
|
||||||
|
if (pp->rx_en && tegra_hv_ivc_can_read(pp->ivck))
|
||||||
|
tegra_hv_comm_rx_chars(pp);
|
||||||
|
|
||||||
|
tegra_hv_comm_tx_chars(pp);
|
||||||
|
xmit_timer_setup(pp, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_unlock(&port->lock);
|
||||||
|
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_config_port(struct uart_port *port, int flags)
|
||||||
|
{
|
||||||
|
port->type = PORT_GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tegra_hv_comm_startup(struct uart_port *port)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
unsigned long flags;
|
||||||
|
int ret, irq;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
irq = pp->ivck->irq;
|
||||||
|
ret = devm_request_irq(dev, irq,
|
||||||
|
tegra_hv_comm_interrupt, 0,
|
||||||
|
dev_name(dev), port);
|
||||||
|
if (ret) {
|
||||||
|
pr_err(DRV_NAME ": unable to request #%d irq=%d\n",
|
||||||
|
port->line, irq);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
spin_lock_irqsave(&port->lock, flags);
|
||||||
|
pp->rx_en = 1;
|
||||||
|
if (tegra_hv_ivc_can_read(pp->ivck))
|
||||||
|
tegra_hv_comm_rx_chars(pp);
|
||||||
|
spin_unlock_irqrestore(&port->lock, flags);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_shutdown(struct uart_port *port)
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = to_tegra_hv_comm(port);
|
||||||
|
struct device *dev = &pp->pdev->dev;
|
||||||
|
unsigned long flags;
|
||||||
|
int irq;
|
||||||
|
|
||||||
|
dev_dbg(dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
irq = pp->ivck->irq;
|
||||||
|
|
||||||
|
devm_free_irq(&pp->pdev->dev, irq, port);
|
||||||
|
|
||||||
|
spin_lock_irqsave(&port->lock, flags);
|
||||||
|
pp->rx_en = 0;
|
||||||
|
if (pp->tx_en) {
|
||||||
|
pp->tx_en = 0;
|
||||||
|
del_timer(&pp->tx_timer);
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(&port->lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *tegra_hv_comm_type(struct uart_port *port)
|
||||||
|
{
|
||||||
|
return (port->type == PORT_GENERIC) ? "Tegra HV" : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tegra_hv_comm_request_port(struct uart_port *port)
|
||||||
|
{
|
||||||
|
/* UARTs always present */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_release_port(struct uart_port *port)
|
||||||
|
{
|
||||||
|
/* Nothing to release... */
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tegra_hv_comm_verify_port(struct uart_port *port,
|
||||||
|
struct serial_struct *ser)
|
||||||
|
{
|
||||||
|
if (ser->type != PORT_UNKNOWN && ser->type != PORT_GENERIC)
|
||||||
|
return -EINVAL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Define the basic serial functions we support.
|
||||||
|
*/
|
||||||
|
static struct uart_ops tegra_hv_comm_ops = {
|
||||||
|
.tx_empty = tegra_hv_comm_tx_empty,
|
||||||
|
.get_mctrl = tegra_hv_comm_get_mctrl,
|
||||||
|
.set_mctrl = tegra_hv_comm_set_mctrl,
|
||||||
|
.start_tx = tegra_hv_comm_start_tx,
|
||||||
|
.stop_tx = tegra_hv_comm_stop_tx,
|
||||||
|
.stop_rx = tegra_hv_comm_stop_rx,
|
||||||
|
.enable_ms = tegra_hv_comm_enable_ms,
|
||||||
|
.break_ctl = tegra_hv_comm_break_ctl,
|
||||||
|
.startup = tegra_hv_comm_startup,
|
||||||
|
.shutdown = tegra_hv_comm_shutdown,
|
||||||
|
.set_termios = tegra_hv_comm_set_termios,
|
||||||
|
.type = tegra_hv_comm_type,
|
||||||
|
.request_port = tegra_hv_comm_request_port,
|
||||||
|
.release_port = tegra_hv_comm_release_port,
|
||||||
|
.config_port = tegra_hv_comm_config_port,
|
||||||
|
.verify_port = tegra_hv_comm_verify_port,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define TEGRA_HV_COMM_MAJOR 204
|
||||||
|
#define TEGRA_HV_COMM_MINOR 213
|
||||||
|
#define TEGRA_HV_COMM_MAXPORTS 1
|
||||||
|
static struct tegra_hv_comm tegra_hv_comm_ports[TEGRA_HV_COMM_MAXPORTS];
|
||||||
|
|
||||||
|
static struct uart_driver tegra_hv_comm_driver = {
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.driver_name = "tegra_hv_comm",
|
||||||
|
.dev_name = "ttyTGRHVC",
|
||||||
|
.major = TEGRA_HV_COMM_MAJOR,
|
||||||
|
.minor = TEGRA_HV_COMM_MINOR,
|
||||||
|
.nr = TEGRA_HV_COMM_MAXPORTS,
|
||||||
|
.cons = NULL, /* no console supported yet */
|
||||||
|
};
|
||||||
|
|
||||||
|
static int tegra_hv_comm_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct device_node *dn, *hv_dn;
|
||||||
|
struct tegra_hv_comm *pp;
|
||||||
|
struct uart_port *port;
|
||||||
|
int i = pdev->id;
|
||||||
|
int ret;
|
||||||
|
u32 id;
|
||||||
|
|
||||||
|
if (!is_tegra_hypervisor_mode()) {
|
||||||
|
dev_info(dev, "Hypervisor is not present\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the platform_device does not have an ID (i.e., no numerical suffix in
|
||||||
|
* its device node name), treat it as having one port.
|
||||||
|
*/
|
||||||
|
if (i < 0)
|
||||||
|
i = 0;
|
||||||
|
|
||||||
|
if (i >= TEGRA_HV_COMM_MAXPORTS)
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
dn = dev->of_node;
|
||||||
|
if (dn == NULL) {
|
||||||
|
dev_err(dev, "No OF data\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
hv_dn = of_parse_phandle(dn, "ivc", 0);
|
||||||
|
if (hv_dn == NULL) {
|
||||||
|
dev_err(dev, "Failed to parse phandle of ivc prop\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = of_property_read_u32_index(dn, "ivc", 1, &id);
|
||||||
|
if (ret != 0) {
|
||||||
|
dev_err(dev, "Failed to read IVC property ID\n");
|
||||||
|
of_node_put(hv_dn);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* zero out */
|
||||||
|
pp = &tegra_hv_comm_ports[i];
|
||||||
|
memset(pp, 0, sizeof(*pp));
|
||||||
|
|
||||||
|
port = &pp->port;
|
||||||
|
|
||||||
|
pp->ivck = tegra_hv_ivc_reserve(hv_dn, id, NULL);
|
||||||
|
|
||||||
|
of_node_put(hv_dn);
|
||||||
|
|
||||||
|
if (IS_ERR_OR_NULL(pp->ivck)) {
|
||||||
|
dev_err(dev, "Failed to reserve IVC channel %d\n", id);
|
||||||
|
ret = PTR_ERR(pp->ivck);
|
||||||
|
pp->ivck = NULL;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* make sure the frame size is sufficient */
|
||||||
|
if (pp->ivck->frame_size <= HDR_SIZE) {
|
||||||
|
dev_err(dev, "frame size too small to support COMM\n");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out_unreserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(dev, "Reserved IVC channel #%d - frame_size=%d\n",
|
||||||
|
id, pp->ivck->frame_size);
|
||||||
|
|
||||||
|
/* allocate temporary frames */
|
||||||
|
pp->buf = devm_kzalloc(dev, pp->ivck->frame_size * 2, GFP_KERNEL);
|
||||||
|
if (pp->buf == NULL) {
|
||||||
|
dev_err(dev, "Failed to allocate buffers\n");
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto out_unreserve;
|
||||||
|
}
|
||||||
|
pp->rx_frame = pp->buf;
|
||||||
|
pp->rx_buf = pp->rx_frame + HDR_SIZE;
|
||||||
|
pp->tx_frame = pp->rx_frame + pp->ivck->frame_size;
|
||||||
|
pp->tx_buf = pp->tx_frame + HDR_SIZE;
|
||||||
|
|
||||||
|
timer_setup(&pp->tx_timer, tegra_hv_tx_timer_expired, 0);
|
||||||
|
|
||||||
|
pp->pdev = pdev;
|
||||||
|
platform_set_drvdata(pdev, pp);
|
||||||
|
|
||||||
|
port->line = i;
|
||||||
|
port->type = PORT_GENERIC;
|
||||||
|
port->iotype = SERIAL_IO_MEM;
|
||||||
|
port->ops = &tegra_hv_comm_ops;
|
||||||
|
port->flags = UPF_BOOT_AUTOCONF;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* start the channel reset process asynchronously. until the reset
|
||||||
|
* process completes, any attempt to use the ivc channel will return
|
||||||
|
* an error (e.g., all transmits will fail.)
|
||||||
|
*/
|
||||||
|
tegra_hv_ivc_channel_reset(pp->ivck);
|
||||||
|
|
||||||
|
ret = uart_add_one_port(&tegra_hv_comm_driver, port);
|
||||||
|
if (ret != 0) {
|
||||||
|
dev_err(dev, "uart_add_one_port failed\n");
|
||||||
|
goto out_unreserve;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(dev, "ready\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
out_unreserve:
|
||||||
|
tegra_hv_ivc_unreserve(pp->ivck);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
|
||||||
|
static void tegra_hv_comm_remove(struct platform_device *pdev)
|
||||||
|
#else
|
||||||
|
static int tegra_hv_comm_remove(struct platform_device *pdev)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
struct tegra_hv_comm *pp = platform_get_drvdata(pdev);
|
||||||
|
struct uart_port *port;
|
||||||
|
|
||||||
|
port = &pp->port;
|
||||||
|
|
||||||
|
tegra_hv_ivc_unreserve(pp->ivck);
|
||||||
|
|
||||||
|
uart_remove_one_port(&tegra_hv_comm_driver, port);
|
||||||
|
|
||||||
|
#if !defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID)
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static struct of_device_id tegra_hv_comm_match[] = {
|
||||||
|
{ .compatible = "nvidia,tegra-hv-comm", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, tegra_hv_comm_match);
|
||||||
|
#endif /* CONFIG_OF */
|
||||||
|
|
||||||
|
static struct platform_driver tegra_hv_comm_platform_driver = {
|
||||||
|
.probe = tegra_hv_comm_probe,
|
||||||
|
.remove = tegra_hv_comm_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = DRV_NAME,
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
.of_match_table = of_match_ptr(tegra_hv_comm_match),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int tegra_hv_comm_init(void)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
rc = uart_register_driver(&tegra_hv_comm_driver);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
rc = platform_driver_register(&tegra_hv_comm_platform_driver);
|
||||||
|
if (rc)
|
||||||
|
uart_unregister_driver(&tegra_hv_comm_driver);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tegra_hv_comm_exit(void)
|
||||||
|
{
|
||||||
|
platform_driver_unregister(&tegra_hv_comm_platform_driver);
|
||||||
|
uart_unregister_driver(&tegra_hv_comm_driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
module_init(tegra_hv_comm_init);
|
||||||
|
module_exit(tegra_hv_comm_exit);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Pantelis Antoniou <pantoniou@nvidia.com>");
|
||||||
|
MODULE_DESCRIPTION("TTY over Tegra Hypervisor IVC channel");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
@@ -230,6 +230,7 @@ NV_CONFTEST_FUNCTION_COMPILE_TESTS += timer_delete
|
|||||||
NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_send_xchar_has_u8_arg
|
NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_send_xchar_has_u8_arg
|
||||||
NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_write_has_u8_ptr_arg
|
NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_write_has_u8_ptr_arg
|
||||||
NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_set_termios_has_const_ktermios_arg
|
NV_CONFTEST_FUNCTION_COMPILE_TESTS += tty_operations_struct_set_termios_has_const_ktermios_arg
|
||||||
|
NV_CONFTEST_FUNCTION_COMPILE_TESTS += uart_state_has_xmit
|
||||||
NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_has_set_dma_mask
|
NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_has_set_dma_mask
|
||||||
NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_pwr_change_notify_has_const_arg
|
NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_pwr_change_notify_has_const_arg
|
||||||
NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_suspend_has_status_arg
|
NV_CONFTEST_FUNCTION_COMPILE_TESTS += ufs_hba_variant_ops_suspend_has_status_arg
|
||||||
|
|||||||
@@ -8810,6 +8810,22 @@ compile_test() {
|
|||||||
compile_check_conftest "$CODE" "NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG" "" "types"
|
compile_check_conftest "$CODE" "NV_TTY_OPERATIONS_STRUCT_SET_TERMIOS_HAS_CONST_KTERMIOS_ARG" "" "types"
|
||||||
;;
|
;;
|
||||||
|
|
||||||
|
uart_state_has_xmit)
|
||||||
|
#
|
||||||
|
# Determine if the struct uart_state has a xmit member.
|
||||||
|
#
|
||||||
|
# Commit 1788cf6 ("tty: serial: switch from circ_buf to kfifo") in
|
||||||
|
# Linux 6.10 removed this member.
|
||||||
|
#
|
||||||
|
CODE="
|
||||||
|
#include <linux/serial_core.h>
|
||||||
|
int conftest_uart_state_has_xmit(void) {
|
||||||
|
return offsetof(struct uart_state, xmit);
|
||||||
|
}"
|
||||||
|
|
||||||
|
compile_check_conftest "$CODE" "NV_UART_STATE_HAS_XMIT" "" "types"
|
||||||
|
;;
|
||||||
|
|
||||||
ufs_hba_variant_ops_has_set_dma_mask)
|
ufs_hba_variant_ops_has_set_dma_mask)
|
||||||
#
|
#
|
||||||
# Determine if the 'struct ufs_hba_variant_ops' has a 'set_dma_mask'
|
# Determine if the 'struct ufs_hba_variant_ops' has a 'set_dma_mask'
|
||||||
|
|||||||
Reference in New Issue
Block a user