mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
serial: 8250: Add Airoha SoC UART and HSUART support
Add support for Airoha AN7523 UART and AN7581 HSUART. These implement a standard 16550 UART with only some custom logic for baud rate handling. Co-developed-by: Benjamin Larsson <benjamin.larsson@genexis.eu> Signed-off-by: Benjamin Larsson <benjamin.larsson@genexis.eu> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com> Link: https://patch.msgid.link/20260811080950.664379-4-ansuelsmth@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
bce284b00e
commit
92a8133c44
|
|
@ -73,6 +73,12 @@ struct serial8250_config {
|
|||
unsigned int flags;
|
||||
};
|
||||
|
||||
enum uart_port_type {
|
||||
/* Internal 8250 only */
|
||||
UART_PORT_AIROHA = 124,
|
||||
UART_PORT_AIROHA_HS = 125,
|
||||
};
|
||||
|
||||
#define UART_CAP_FIFO BIT(8) /* UART has FIFO */
|
||||
#define UART_CAP_EFR BIT(9) /* UART has EFR */
|
||||
#define UART_CAP_SLEEP BIT(10) /* UART has IER sleep */
|
||||
|
|
|
|||
187
drivers/tty/serial/8250/8250_airoha.c
Normal file
187
drivers/tty/serial/8250/8250_airoha.c
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Airoha UART driver
|
||||
*
|
||||
* Copyright (c) 2025 Genexis Sweden AB
|
||||
* Author: Benjamin Larsson <benjamin.larsson@genexis.eu>
|
||||
* Christian Marangi <ansuelsmth@gmail.com>
|
||||
*/
|
||||
|
||||
#include <linux/array_size.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/property.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/serial_8250.h>
|
||||
#include <linux/serial_core.h>
|
||||
#include <linux/serial_reg.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/units.h>
|
||||
|
||||
#include "8250.h"
|
||||
|
||||
#define UART_AIROHA_XINCLKDR 10
|
||||
#define UART_AIROHA_XYD 11
|
||||
#define UART_AIROHA_XYD_X GENMASK(31, 16)
|
||||
#define UART_AIROHA_XYD_Y GENMASK(15, 0)
|
||||
|
||||
struct airoha_8250_priv {
|
||||
int line;
|
||||
};
|
||||
|
||||
#define UART_BRD_20M 0x0001
|
||||
|
||||
#define XINDIV_CLOCK (20 * HZ_PER_MHZ)
|
||||
#define XYD_Y 65000
|
||||
|
||||
static const unsigned int airoha_clk_divs[] = { 2, 4, 10 };
|
||||
|
||||
static unsigned int airoha_get_divisor(struct uart_port *port,
|
||||
unsigned int baud,
|
||||
unsigned int *frac)
|
||||
{
|
||||
/* Hardware always uses BRDIV = 1. */
|
||||
*frac = 0;
|
||||
|
||||
return UART_BRD_20M;
|
||||
}
|
||||
|
||||
/*
|
||||
* Airoha UART baud rate calculation logic
|
||||
*
|
||||
* crystal_clock = 20 MHz (fixed frequency)
|
||||
* xindiv_clock = crystal_clock / clock_div
|
||||
* (x/y) = XYD, 32 bit register with 16 bits of x and then 16 bits of y
|
||||
* clock_div = XINCLK_DIVCNT (default set to 10 (0x4)),
|
||||
* - 3 bit register [ 1, 2, 4, 8, 10, 12, 16, 20 ]
|
||||
*
|
||||
* baud_rate = ((xindiv_clock) * (x/y)) / ([BRDH,BRDL] * 16)
|
||||
*
|
||||
* Selecting divider needs to fulfill
|
||||
* 1.8432 MHz <= xindiv_clk <= APB clock / 2
|
||||
* The clocks are unknown but a divider of value 1 did not result in a valid
|
||||
* waveform.
|
||||
*
|
||||
* XYD_y seems to need to be larger then XYD_x for proper waveform generation.
|
||||
* Setting [BRDH,BRDL] to [0,1] and XYD_y to 65000 gives even values
|
||||
* for usual baud rates.
|
||||
*/
|
||||
static void airoha_set_divisor(struct uart_port *port, unsigned int baud,
|
||||
unsigned int quot, unsigned int quot_frac)
|
||||
{
|
||||
struct uart_8250_port *up = up_to_u8250p(port);
|
||||
u32 xindiv_clk;
|
||||
u64 xyd_x, nom;
|
||||
int div_bit;
|
||||
|
||||
/* Set baud rate calculation defaults (BRDIV [BRDH,BRDL] to 1) */
|
||||
serial8250_do_set_divisor(port, baud, UART_BRD_20M);
|
||||
|
||||
/*
|
||||
* Calculate XYD_x and XINCLKDR register by searching
|
||||
* through a table of crystal_clock divisors.
|
||||
*/
|
||||
nom = (u64)baud * XYD_Y;
|
||||
for (div_bit = ARRAY_SIZE(airoha_clk_divs) - 1; div_bit >= 0; div_bit--) {
|
||||
unsigned int div = airoha_clk_divs[div_bit];
|
||||
|
||||
xindiv_clk = XINDIV_CLOCK / div;
|
||||
xyd_x = div_u64(nom * 16, xindiv_clk);
|
||||
|
||||
/* For the HSUART xyd_x needs to be scaled by a factor of 2 */
|
||||
if (port->type == UART_PORT_AIROHA_HS)
|
||||
xyd_x /= 2;
|
||||
|
||||
if (xyd_x < XYD_Y)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Couldn't find a valid xyd_x */
|
||||
if (div_bit < 0) {
|
||||
dev_err(port->dev, "failed to find suitable clock divisor for baud %u\n",
|
||||
baud);
|
||||
return;
|
||||
}
|
||||
|
||||
serial_port_out(port, UART_AIROHA_XINCLKDR, BIT(div_bit));
|
||||
serial_port_out(port, UART_AIROHA_XYD,
|
||||
FIELD_PREP(UART_AIROHA_XYD_X, xyd_x) |
|
||||
FIELD_PREP(UART_AIROHA_XYD_Y, XYD_Y));
|
||||
|
||||
/* Restore normal register access. */
|
||||
serial_port_out(port, UART_LCR, up->lcr);
|
||||
}
|
||||
|
||||
static int airoha_8250_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct uart_8250_port uart = { };
|
||||
struct device *dev = &pdev->dev;
|
||||
struct airoha_8250_priv *priv;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return dev_err_probe(dev, -EINVAL, "invalid address\n");
|
||||
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
uart.port.dev = dev;
|
||||
if (device_is_compatible(dev, "airoha,an7581-hsuart"))
|
||||
uart.port.type = UART_PORT_AIROHA_HS;
|
||||
else
|
||||
uart.port.type = UART_PORT_AIROHA;
|
||||
uart.port.flags = UPF_BOOT_AUTOCONF | UPF_FIXED_PORT |
|
||||
UPF_FIXED_TYPE | UPF_IOREMAP;
|
||||
uart.port.set_divisor = airoha_set_divisor;
|
||||
uart.port.get_divisor = airoha_get_divisor;
|
||||
uart.port.mapbase = res->start;
|
||||
uart.port.mapsize = resource_size(res);
|
||||
|
||||
ret = uart_read_and_validate_port_properties(&uart.port);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = serial8250_register_8250_port(&uart);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
priv->line = ret;
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void airoha_8250_remove(struct platform_device *ofdev)
|
||||
{
|
||||
struct airoha_8250_priv *priv = platform_get_drvdata(ofdev);
|
||||
|
||||
serial8250_unregister_port(priv->line);
|
||||
}
|
||||
|
||||
static const struct of_device_id airoha_8250_dt_ids[] = {
|
||||
{ .compatible = "airoha,en7523-uart" },
|
||||
{ .compatible = "airoha,an7581-hsuart" },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, airoha_8250_dt_ids);
|
||||
|
||||
static struct platform_driver airoha_8250_driver = {
|
||||
.driver = {
|
||||
.name = "8250_airoha",
|
||||
.of_match_table = airoha_8250_dt_ids,
|
||||
},
|
||||
.probe = airoha_8250_probe,
|
||||
.remove = airoha_8250_remove,
|
||||
};
|
||||
module_platform_driver(airoha_8250_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("Airoha UART driver");
|
||||
MODULE_AUTHOR("Benjamin Larsson <benjamin.larsson@genexis.eu>");
|
||||
MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
|
||||
|
|
@ -319,6 +319,22 @@ static const struct serial8250_config uart_config[] = {
|
|||
.rxtrig_bytes = {15, 31, 63, 111},
|
||||
.flags = UART_CAP_FIFO,
|
||||
},
|
||||
[UART_PORT_AIROHA] = {
|
||||
.name = "Airoha UART",
|
||||
.fifo_size = 8,
|
||||
.tx_loadsz = 1,
|
||||
.fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01 | UART_FCR_CLEAR_RCVR,
|
||||
.rxtrig_bytes = {1, 4, 4, 4},
|
||||
.flags = UART_CAP_FIFO,
|
||||
},
|
||||
[UART_PORT_AIROHA_HS] = {
|
||||
.name = "Airoha HSUART",
|
||||
.fifo_size = 128,
|
||||
.tx_loadsz = 128,
|
||||
.fcr = UART_FCR_ENABLE_FIFO | UART_FCR_R_TRIG_01 | UART_FCR_CLEAR_RCVR,
|
||||
.rxtrig_bytes = {1, 4},
|
||||
.flags = UART_CAP_FIFO,
|
||||
},
|
||||
};
|
||||
|
||||
/* Uart divisor latch read */
|
||||
|
|
|
|||
|
|
@ -347,6 +347,17 @@ config SERIAL_8250_ACORN
|
|||
system, say Y to this option. The driver can handle 1, 2, or 3 port
|
||||
cards. If unsure, say N.
|
||||
|
||||
config SERIAL_8250_AIROHA
|
||||
tristate "Airoha UART support"
|
||||
depends on ARCH_AIROHA || COMPILE_TEST
|
||||
depends on SERIAL_8250
|
||||
help
|
||||
Selecting this option enables an Airoha SoC specific baud rate
|
||||
calculation routine on an otherwise 16550 compatible UART hardware.
|
||||
|
||||
If you have an Airoha based board and want to use the serial port,
|
||||
say Y to this option. If unsure, say N.
|
||||
|
||||
config SERIAL_8250_BCM2835AUX
|
||||
tristate "BCM2835 auxiliar mini UART support"
|
||||
depends on ARCH_BCM2835 || COMPILE_TEST
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ obj-$(CONFIG_SERIAL_8250_CONSOLE) += 8250_early.o
|
|||
|
||||
obj-$(CONFIG_SERIAL_8250_ACCENT) += 8250_accent.o
|
||||
obj-$(CONFIG_SERIAL_8250_ACORN) += 8250_acorn.o
|
||||
obj-$(CONFIG_SERIAL_8250_AIROHA) += 8250_airoha.o
|
||||
obj-$(CONFIG_SERIAL_8250_ASPEED_VUART) += 8250_aspeed_vuart.o
|
||||
obj-$(CONFIG_SERIAL_8250_BCM2835AUX) += 8250_bcm2835aux.o
|
||||
obj-$(CONFIG_SERIAL_8250_BCM7271) += 8250_bcm7271.o
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user