162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  drivers/tty/serial/8250/8250_pxa.c -- driver for PXA on-board UARTS
462306a36Sopenharmony_ci *  Copyright:	(C) 2013 Sergei Ianovich <ynvich@gmail.com>
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *  replaces drivers/serial/pxa.c by Nicolas Pitre
762306a36Sopenharmony_ci *  Created:	Feb 20, 2003
862306a36Sopenharmony_ci *  Copyright:	(C) 2003 Monta Vista Software, Inc.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci *  Based on drivers/serial/8250.c by Russell King.
1162306a36Sopenharmony_ci */
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <linux/device.h>
1462306a36Sopenharmony_ci#include <linux/init.h>
1562306a36Sopenharmony_ci#include <linux/io.h>
1662306a36Sopenharmony_ci#include <linux/module.h>
1762306a36Sopenharmony_ci#include <linux/serial_8250.h>
1862306a36Sopenharmony_ci#include <linux/serial_core.h>
1962306a36Sopenharmony_ci#include <linux/serial_reg.h>
2062306a36Sopenharmony_ci#include <linux/of.h>
2162306a36Sopenharmony_ci#include <linux/of_platform.h>
2262306a36Sopenharmony_ci#include <linux/platform_device.h>
2362306a36Sopenharmony_ci#include <linux/slab.h>
2462306a36Sopenharmony_ci#include <linux/clk.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include "8250.h"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct pxa8250_data {
2962306a36Sopenharmony_ci	int			line;
3062306a36Sopenharmony_ci	struct clk		*clk;
3162306a36Sopenharmony_ci};
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic int __maybe_unused serial_pxa_suspend(struct device *dev)
3462306a36Sopenharmony_ci{
3562306a36Sopenharmony_ci	struct pxa8250_data *data = dev_get_drvdata(dev);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	serial8250_suspend_port(data->line);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return 0;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int __maybe_unused serial_pxa_resume(struct device *dev)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	struct pxa8250_data *data = dev_get_drvdata(dev);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	serial8250_resume_port(data->line);
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	return 0;
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic const struct dev_pm_ops serial_pxa_pm_ops = {
5262306a36Sopenharmony_ci	SET_SYSTEM_SLEEP_PM_OPS(serial_pxa_suspend, serial_pxa_resume)
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_cistatic const struct of_device_id serial_pxa_dt_ids[] = {
5662306a36Sopenharmony_ci	{ .compatible = "mrvl,pxa-uart", },
5762306a36Sopenharmony_ci	{ .compatible = "mrvl,mmp-uart", },
5862306a36Sopenharmony_ci	{}
5962306a36Sopenharmony_ci};
6062306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, serial_pxa_dt_ids);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/* Uart divisor latch write */
6362306a36Sopenharmony_cistatic void serial_pxa_dl_write(struct uart_8250_port *up, u32 value)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	unsigned int dll;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	serial_out(up, UART_DLL, value & 0xff);
6862306a36Sopenharmony_ci	/*
6962306a36Sopenharmony_ci	 * work around Erratum #74 according to Marvel(R) PXA270M Processor
7062306a36Sopenharmony_ci	 * Specification Update (April 19, 2010)
7162306a36Sopenharmony_ci	 */
7262306a36Sopenharmony_ci	dll = serial_in(up, UART_DLL);
7362306a36Sopenharmony_ci	WARN_ON(dll != (value & 0xff));
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	serial_out(up, UART_DLM, value >> 8 & 0xff);
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic void serial_pxa_pm(struct uart_port *port, unsigned int state,
8062306a36Sopenharmony_ci	      unsigned int oldstate)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	struct pxa8250_data *data = port->private_data;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	if (!state)
8562306a36Sopenharmony_ci		clk_prepare_enable(data->clk);
8662306a36Sopenharmony_ci	else
8762306a36Sopenharmony_ci		clk_disable_unprepare(data->clk);
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int serial_pxa_probe(struct platform_device *pdev)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct uart_8250_port uart = {};
9362306a36Sopenharmony_ci	struct pxa8250_data *data;
9462306a36Sopenharmony_ci	struct resource *mmres;
9562306a36Sopenharmony_ci	int irq, ret;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
9862306a36Sopenharmony_ci	if (irq < 0)
9962306a36Sopenharmony_ci		return irq;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	mmres = platform_get_resource(pdev, IORESOURCE_MEM, 0);
10262306a36Sopenharmony_ci	if (!mmres)
10362306a36Sopenharmony_ci		return -ENODEV;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
10662306a36Sopenharmony_ci	if (!data)
10762306a36Sopenharmony_ci		return -ENOMEM;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	data->clk = devm_clk_get(&pdev->dev, NULL);
11062306a36Sopenharmony_ci	if (IS_ERR(data->clk))
11162306a36Sopenharmony_ci		return PTR_ERR(data->clk);
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	ret = clk_prepare(data->clk);
11462306a36Sopenharmony_ci	if (ret)
11562306a36Sopenharmony_ci		return ret;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	ret = of_alias_get_id(pdev->dev.of_node, "serial");
11862306a36Sopenharmony_ci	if (ret >= 0)
11962306a36Sopenharmony_ci		uart.port.line = ret;
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	uart.port.type = PORT_XSCALE;
12262306a36Sopenharmony_ci	uart.port.iotype = UPIO_MEM32;
12362306a36Sopenharmony_ci	uart.port.mapbase = mmres->start;
12462306a36Sopenharmony_ci	uart.port.regshift = 2;
12562306a36Sopenharmony_ci	uart.port.irq = irq;
12662306a36Sopenharmony_ci	uart.port.fifosize = 64;
12762306a36Sopenharmony_ci	uart.port.flags = UPF_IOREMAP | UPF_SKIP_TEST | UPF_FIXED_TYPE;
12862306a36Sopenharmony_ci	uart.port.dev = &pdev->dev;
12962306a36Sopenharmony_ci	uart.port.uartclk = clk_get_rate(data->clk);
13062306a36Sopenharmony_ci	uart.port.pm = serial_pxa_pm;
13162306a36Sopenharmony_ci	uart.port.private_data = data;
13262306a36Sopenharmony_ci	uart.dl_write = serial_pxa_dl_write;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	ret = serial8250_register_8250_port(&uart);
13562306a36Sopenharmony_ci	if (ret < 0)
13662306a36Sopenharmony_ci		goto err_clk;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	data->line = ret;
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	platform_set_drvdata(pdev, data);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	return 0;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci err_clk:
14562306a36Sopenharmony_ci	clk_unprepare(data->clk);
14662306a36Sopenharmony_ci	return ret;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_cistatic int serial_pxa_remove(struct platform_device *pdev)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	struct pxa8250_data *data = platform_get_drvdata(pdev);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	serial8250_unregister_port(data->line);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	clk_unprepare(data->clk);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	return 0;
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_cistatic struct platform_driver serial_pxa_driver = {
16162306a36Sopenharmony_ci	.probe          = serial_pxa_probe,
16262306a36Sopenharmony_ci	.remove         = serial_pxa_remove,
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	.driver		= {
16562306a36Sopenharmony_ci		.name	= "pxa2xx-uart",
16662306a36Sopenharmony_ci		.pm	= &serial_pxa_pm_ops,
16762306a36Sopenharmony_ci		.of_match_table = serial_pxa_dt_ids,
16862306a36Sopenharmony_ci	},
16962306a36Sopenharmony_ci};
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cimodule_platform_driver(serial_pxa_driver);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci#ifdef CONFIG_SERIAL_8250_CONSOLE
17462306a36Sopenharmony_cistatic int __init early_serial_pxa_setup(struct earlycon_device *device,
17562306a36Sopenharmony_ci				  const char *options)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	struct uart_port *port = &device->port;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (!(device->port.membase || device->port.iobase))
18062306a36Sopenharmony_ci		return -ENODEV;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	port->regshift = 2;
18362306a36Sopenharmony_ci	return early_serial8250_setup(device, NULL);
18462306a36Sopenharmony_ci}
18562306a36Sopenharmony_ciOF_EARLYCON_DECLARE(early_pxa, "mrvl,pxa-uart", early_serial_pxa_setup);
18662306a36Sopenharmony_ciOF_EARLYCON_DECLARE(mmp, "mrvl,mmp-uart", early_serial_pxa_setup);
18762306a36Sopenharmony_ci#endif
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ciMODULE_AUTHOR("Sergei Ianovich");
19062306a36Sopenharmony_ciMODULE_LICENSE("GPL");
19162306a36Sopenharmony_ciMODULE_ALIAS("platform:pxa2xx-uart");
192