forked from torvalds/linux
-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
serial: 8250_tegra: Create Tegra specific 8250 driver
To support booting NVIDIA Tegra platforms with either Device-Tree or ACPI, create a Tegra specific 8250 serial driver that supports both firmware types. Another benefit from doing this, is that the Tegra specific codec in the generic Open Firmware 8250 driver can now be removed. Signed-off-by: Jeff Brasen <[email protected]> Signed-off-by: Jon Hunter <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
- Loading branch information
Showing
4 changed files
with
208 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
// SPDX-License-Identifier: GPL-2.0+ | ||
/* | ||
* Serial Port driver for Tegra devices | ||
* | ||
* Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. | ||
*/ | ||
|
||
#include <linux/acpi.h> | ||
#include <linux/clk.h> | ||
#include <linux/console.h> | ||
#include <linux/delay.h> | ||
#include <linux/io.h> | ||
#include <linux/module.h> | ||
#include <linux/reset.h> | ||
#include <linux/slab.h> | ||
|
||
#include "8250.h" | ||
|
||
struct tegra_uart { | ||
struct clk *clk; | ||
struct reset_control *rst; | ||
int line; | ||
}; | ||
|
||
static void tegra_uart_handle_break(struct uart_port *p) | ||
{ | ||
unsigned int status, tmout = 10000; | ||
|
||
do { | ||
status = p->serial_in(p, UART_LSR); | ||
if (status & (UART_LSR_FIFOE | UART_LSR_BRK_ERROR_BITS)) | ||
status = p->serial_in(p, UART_RX); | ||
else | ||
break; | ||
if (--tmout == 0) | ||
break; | ||
udelay(1); | ||
} while (1); | ||
} | ||
|
||
static int tegra_uart_probe(struct platform_device *pdev) | ||
{ | ||
struct uart_8250_port port8250; | ||
struct tegra_uart *uart; | ||
struct uart_port *port; | ||
struct resource *res; | ||
int ret; | ||
|
||
uart = devm_kzalloc(&pdev->dev, sizeof(*uart), GFP_KERNEL); | ||
if (!uart) | ||
return -ENOMEM; | ||
|
||
memset(&port8250, 0, sizeof(port8250)); | ||
|
||
port = &port8250.port; | ||
spin_lock_init(&port->lock); | ||
|
||
port->flags = UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF | UPF_FIXED_PORT | | ||
UPF_FIXED_TYPE; | ||
port->iotype = UPIO_MEM32; | ||
port->regshift = 2; | ||
port->type = PORT_TEGRA; | ||
port->irqflags |= IRQF_SHARED; | ||
port->dev = &pdev->dev; | ||
port->handle_break = tegra_uart_handle_break; | ||
|
||
ret = of_alias_get_id(pdev->dev.of_node, "serial"); | ||
if (ret >= 0) | ||
port->line = ret; | ||
|
||
ret = platform_get_irq(pdev, 0); | ||
if (ret < 0) | ||
return ret; | ||
|
||
port->irq = ret; | ||
|
||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
if (!res) | ||
return -ENODEV; | ||
|
||
port->membase = devm_ioremap(&pdev->dev, res->start, | ||
resource_size(res)); | ||
if (!port->membase) | ||
return -ENOMEM; | ||
|
||
port->mapbase = res->start; | ||
port->mapsize = resource_size(res); | ||
|
||
uart->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL); | ||
if (IS_ERR(uart->rst)) | ||
return PTR_ERR(uart->rst); | ||
|
||
if (device_property_read_u32(&pdev->dev, "clock-frequency", | ||
&port->uartclk)) { | ||
uart->clk = devm_clk_get(&pdev->dev, NULL); | ||
if (IS_ERR(uart->clk)) { | ||
dev_err(&pdev->dev, "failed to get clock!\n"); | ||
return -ENODEV; | ||
} | ||
|
||
ret = clk_prepare_enable(uart->clk); | ||
if (ret < 0) | ||
return ret; | ||
|
||
port->uartclk = clk_get_rate(uart->clk); | ||
} | ||
|
||
ret = reset_control_deassert(uart->rst); | ||
if (ret) | ||
goto err_clkdisable; | ||
|
||
ret = serial8250_register_8250_port(&port8250); | ||
if (ret < 0) | ||
goto err_clkdisable; | ||
|
||
platform_set_drvdata(pdev, uart); | ||
uart->line = ret; | ||
|
||
return 0; | ||
|
||
err_clkdisable: | ||
clk_disable_unprepare(uart->clk); | ||
|
||
return ret; | ||
} | ||
|
||
static int tegra_uart_remove(struct platform_device *pdev) | ||
{ | ||
struct tegra_uart *uart = platform_get_drvdata(pdev); | ||
|
||
serial8250_unregister_port(uart->line); | ||
reset_control_assert(uart->rst); | ||
clk_disable_unprepare(uart->clk); | ||
|
||
return 0; | ||
} | ||
|
||
#ifdef CONFIG_PM_SLEEP | ||
static int tegra_uart_suspend(struct device *dev) | ||
{ | ||
struct tegra_uart *uart = dev_get_drvdata(dev); | ||
struct uart_8250_port *port8250 = serial8250_get_port(uart->line); | ||
struct uart_port *port = &port8250->port; | ||
|
||
serial8250_suspend_port(uart->line); | ||
|
||
if (!uart_console(port) || console_suspend_enabled) | ||
clk_disable_unprepare(uart->clk); | ||
|
||
return 0; | ||
} | ||
|
||
static int tegra_uart_resume(struct device *dev) | ||
{ | ||
struct tegra_uart *uart = dev_get_drvdata(dev); | ||
struct uart_8250_port *port8250 = serial8250_get_port(uart->line); | ||
struct uart_port *port = &port8250->port; | ||
|
||
if (!uart_console(port) || console_suspend_enabled) | ||
clk_prepare_enable(uart->clk); | ||
|
||
serial8250_resume_port(uart->line); | ||
|
||
return 0; | ||
} | ||
#endif | ||
|
||
static SIMPLE_DEV_PM_OPS(tegra_uart_pm_ops, tegra_uart_suspend, | ||
tegra_uart_resume); | ||
|
||
static const struct of_device_id tegra_uart_of_match[] = { | ||
{ .compatible = "nvidia,tegra20-uart", }, | ||
{ }, | ||
}; | ||
MODULE_DEVICE_TABLE(of, tegra_uart_of_match); | ||
|
||
static const struct acpi_device_id tegra_uart_acpi_match[] = { | ||
{ "NVDA0100", 0 }, | ||
{ }, | ||
}; | ||
MODULE_DEVICE_TABLE(acpi, tegra_uart_acpi_match); | ||
|
||
static struct platform_driver tegra_uart_driver = { | ||
.driver = { | ||
.name = "tegra-uart", | ||
.pm = &tegra_uart_pm_ops, | ||
.of_match_table = tegra_uart_of_match, | ||
.acpi_match_table = ACPI_PTR(tegra_uart_acpi_match), | ||
}, | ||
.probe = tegra_uart_probe, | ||
.remove = tegra_uart_remove, | ||
}; | ||
|
||
module_platform_driver(tegra_uart_driver); | ||
|
||
MODULE_AUTHOR("Jeff Brasen <[email protected]>"); | ||
MODULE_DESCRIPTION("NVIDIA Tegra 8250 Driver"); | ||
MODULE_LICENSE("GPL v2"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters