18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Driver for the 98626/98644/internal serial interface on hp300/hp400
48c2ecf20Sopenharmony_ci * (based on the National Semiconductor INS8250/NS16550AF/WD16C552 UARTs)
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Ported from 2.2 and modified to use the normal 8250 driver
78c2ecf20Sopenharmony_ci * by Kars de Jong <jongk@linux-m68k.org>, May 2004.
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/string.h>
128c2ecf20Sopenharmony_ci#include <linux/kernel.h>
138c2ecf20Sopenharmony_ci#include <linux/serial.h>
148c2ecf20Sopenharmony_ci#include <linux/serial_8250.h>
158c2ecf20Sopenharmony_ci#include <linux/delay.h>
168c2ecf20Sopenharmony_ci#include <linux/dio.h>
178c2ecf20Sopenharmony_ci#include <linux/console.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <asm/io.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#include "8250.h"
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#if !defined(CONFIG_HPDCA) && !defined(CONFIG_HPAPCI) && !defined(CONFIG_COMPILE_TEST)
248c2ecf20Sopenharmony_ci#warning CONFIG_SERIAL_8250 defined but neither CONFIG_HPDCA nor CONFIG_HPAPCI defined, are you sure?
258c2ecf20Sopenharmony_ci#endif
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#ifdef CONFIG_HPAPCI
288c2ecf20Sopenharmony_cistruct hp300_port {
298c2ecf20Sopenharmony_ci	struct hp300_port *next;	/* next port */
308c2ecf20Sopenharmony_ci	int line;			/* line (tty) number */
318c2ecf20Sopenharmony_ci};
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic struct hp300_port *hp300_ports;
348c2ecf20Sopenharmony_ci#endif
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#ifdef CONFIG_HPDCA
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic int hpdca_init_one(struct dio_dev *d,
398c2ecf20Sopenharmony_ci					const struct dio_device_id *ent);
408c2ecf20Sopenharmony_cistatic void hpdca_remove_one(struct dio_dev *d);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic struct dio_device_id hpdca_dio_tbl[] = {
438c2ecf20Sopenharmony_ci	{ DIO_ID_DCA0 },
448c2ecf20Sopenharmony_ci	{ DIO_ID_DCA0REM },
458c2ecf20Sopenharmony_ci	{ DIO_ID_DCA1 },
468c2ecf20Sopenharmony_ci	{ DIO_ID_DCA1REM },
478c2ecf20Sopenharmony_ci	{ 0 }
488c2ecf20Sopenharmony_ci};
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic struct dio_driver hpdca_driver = {
518c2ecf20Sopenharmony_ci	.name      = "hpdca",
528c2ecf20Sopenharmony_ci	.id_table  = hpdca_dio_tbl,
538c2ecf20Sopenharmony_ci	.probe     = hpdca_init_one,
548c2ecf20Sopenharmony_ci	.remove    = hpdca_remove_one,
558c2ecf20Sopenharmony_ci};
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci#endif
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic unsigned int num_ports;
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ciextern int hp300_uart_scode;
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci/* Offset to UART registers from base of DCA */
648c2ecf20Sopenharmony_ci#define UART_OFFSET	17
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci#define DCA_ID		0x01	/* ID (read), reset (write) */
678c2ecf20Sopenharmony_ci#define DCA_IC		0x03	/* Interrupt control        */
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci/* Interrupt control */
708c2ecf20Sopenharmony_ci#define DCA_IC_IE	0x80	/* Master interrupt enable  */
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci#define HPDCA_BAUD_BASE 153600
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci/* Base address of the Frodo part */
758c2ecf20Sopenharmony_ci#define FRODO_BASE	(0x41c000)
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci/*
788c2ecf20Sopenharmony_ci * Where we find the 8250-like APCI ports, and how far apart they are.
798c2ecf20Sopenharmony_ci */
808c2ecf20Sopenharmony_ci#define FRODO_APCIBASE		0x0
818c2ecf20Sopenharmony_ci#define FRODO_APCISPACE		0x20
828c2ecf20Sopenharmony_ci#define FRODO_APCI_OFFSET(x)	(FRODO_APCIBASE + ((x) * FRODO_APCISPACE))
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci#define HPAPCI_BAUD_BASE 500400
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci#ifdef CONFIG_SERIAL_8250_CONSOLE
878c2ecf20Sopenharmony_ci/*
888c2ecf20Sopenharmony_ci * Parse the bootinfo to find descriptions for headless console and
898c2ecf20Sopenharmony_ci * debug serial ports and register them with the 8250 driver.
908c2ecf20Sopenharmony_ci */
918c2ecf20Sopenharmony_ciint __init hp300_setup_serial_console(void)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	int scode;
948c2ecf20Sopenharmony_ci	struct uart_port port;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	memset(&port, 0, sizeof(port));
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	if (hp300_uart_scode < 0 || hp300_uart_scode > DIO_SCMAX)
998c2ecf20Sopenharmony_ci		return 0;
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	if (DIO_SCINHOLE(hp300_uart_scode))
1028c2ecf20Sopenharmony_ci		return 0;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	scode = hp300_uart_scode;
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	/* Memory mapped I/O */
1078c2ecf20Sopenharmony_ci	port.iotype = UPIO_MEM;
1088c2ecf20Sopenharmony_ci	port.flags = UPF_SKIP_TEST | UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF;
1098c2ecf20Sopenharmony_ci	port.type = PORT_UNKNOWN;
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	/* Check for APCI console */
1128c2ecf20Sopenharmony_ci	if (scode == 256) {
1138c2ecf20Sopenharmony_ci#ifdef CONFIG_HPAPCI
1148c2ecf20Sopenharmony_ci		pr_info("Serial console is HP APCI 1\n");
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci		port.uartclk = HPAPCI_BAUD_BASE * 16;
1178c2ecf20Sopenharmony_ci		port.mapbase = (FRODO_BASE + FRODO_APCI_OFFSET(1));
1188c2ecf20Sopenharmony_ci		port.membase = (char *)(port.mapbase + DIO_VIRADDRBASE);
1198c2ecf20Sopenharmony_ci		port.regshift = 2;
1208c2ecf20Sopenharmony_ci		add_preferred_console("ttyS", port.line, "9600n8");
1218c2ecf20Sopenharmony_ci#else
1228c2ecf20Sopenharmony_ci		pr_warn("Serial console is APCI but support is disabled (CONFIG_HPAPCI)!\n");
1238c2ecf20Sopenharmony_ci		return 0;
1248c2ecf20Sopenharmony_ci#endif
1258c2ecf20Sopenharmony_ci	} else {
1268c2ecf20Sopenharmony_ci#ifdef CONFIG_HPDCA
1278c2ecf20Sopenharmony_ci		unsigned long pa = dio_scodetophysaddr(scode);
1288c2ecf20Sopenharmony_ci		if (!pa)
1298c2ecf20Sopenharmony_ci			return 0;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci		pr_info("Serial console is HP DCA at select code %d\n", scode);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci		port.uartclk = HPDCA_BAUD_BASE * 16;
1348c2ecf20Sopenharmony_ci		port.mapbase = (pa + UART_OFFSET);
1358c2ecf20Sopenharmony_ci		port.membase = (char *)(port.mapbase + DIO_VIRADDRBASE);
1368c2ecf20Sopenharmony_ci		port.regshift = 1;
1378c2ecf20Sopenharmony_ci		port.irq = DIO_IPL(pa + DIO_VIRADDRBASE);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci		/* Enable board-interrupts */
1408c2ecf20Sopenharmony_ci		out_8(pa + DIO_VIRADDRBASE + DCA_IC, DCA_IC_IE);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci		if (DIO_ID(pa + DIO_VIRADDRBASE) & 0x80)
1438c2ecf20Sopenharmony_ci			add_preferred_console("ttyS", port.line, "9600n8");
1448c2ecf20Sopenharmony_ci#else
1458c2ecf20Sopenharmony_ci		pr_warn("Serial console is DCA but support is disabled (CONFIG_HPDCA)!\n");
1468c2ecf20Sopenharmony_ci		return 0;
1478c2ecf20Sopenharmony_ci#endif
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	if (early_serial_setup(&port) < 0)
1518c2ecf20Sopenharmony_ci		pr_warn("%s: early_serial_setup() failed.\n", __func__);
1528c2ecf20Sopenharmony_ci	return 0;
1538c2ecf20Sopenharmony_ci}
1548c2ecf20Sopenharmony_ci#endif /* CONFIG_SERIAL_8250_CONSOLE */
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci#ifdef CONFIG_HPDCA
1578c2ecf20Sopenharmony_cistatic int hpdca_init_one(struct dio_dev *d,
1588c2ecf20Sopenharmony_ci				const struct dio_device_id *ent)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	struct uart_8250_port uart;
1618c2ecf20Sopenharmony_ci	int line;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci#ifdef CONFIG_SERIAL_8250_CONSOLE
1648c2ecf20Sopenharmony_ci	if (hp300_uart_scode == d->scode) {
1658c2ecf20Sopenharmony_ci		/* Already got it. */
1668c2ecf20Sopenharmony_ci		return 0;
1678c2ecf20Sopenharmony_ci	}
1688c2ecf20Sopenharmony_ci#endif
1698c2ecf20Sopenharmony_ci	memset(&uart, 0, sizeof(uart));
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_ci	/* Memory mapped I/O */
1728c2ecf20Sopenharmony_ci	uart.port.iotype = UPIO_MEM;
1738c2ecf20Sopenharmony_ci	uart.port.flags = UPF_SKIP_TEST | UPF_SHARE_IRQ | UPF_BOOT_AUTOCONF;
1748c2ecf20Sopenharmony_ci	uart.port.irq = d->ipl;
1758c2ecf20Sopenharmony_ci	uart.port.uartclk = HPDCA_BAUD_BASE * 16;
1768c2ecf20Sopenharmony_ci	uart.port.mapbase = (d->resource.start + UART_OFFSET);
1778c2ecf20Sopenharmony_ci	uart.port.membase = (char *)(uart.port.mapbase + DIO_VIRADDRBASE);
1788c2ecf20Sopenharmony_ci	uart.port.regshift = 1;
1798c2ecf20Sopenharmony_ci	uart.port.dev = &d->dev;
1808c2ecf20Sopenharmony_ci	line = serial8250_register_8250_port(&uart);
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	if (line < 0) {
1838c2ecf20Sopenharmony_ci		dev_notice(&d->dev,
1848c2ecf20Sopenharmony_ci			  "8250_hp300: register_serial() DCA scode %d irq %d failed\n",
1858c2ecf20Sopenharmony_ci			  d->scode, uart.port.irq);
1868c2ecf20Sopenharmony_ci		return -ENOMEM;
1878c2ecf20Sopenharmony_ci	}
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	/* Enable board-interrupts */
1908c2ecf20Sopenharmony_ci	out_8(d->resource.start + DIO_VIRADDRBASE + DCA_IC, DCA_IC_IE);
1918c2ecf20Sopenharmony_ci	dio_set_drvdata(d, (void *)line);
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	/* Reset the DCA */
1948c2ecf20Sopenharmony_ci	out_8(d->resource.start + DIO_VIRADDRBASE + DCA_ID, 0xff);
1958c2ecf20Sopenharmony_ci	udelay(100);
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ci	num_ports++;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	return 0;
2008c2ecf20Sopenharmony_ci}
2018c2ecf20Sopenharmony_ci#endif
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_cistatic int __init hp300_8250_init(void)
2048c2ecf20Sopenharmony_ci{
2058c2ecf20Sopenharmony_ci	static int called;
2068c2ecf20Sopenharmony_ci#ifdef CONFIG_HPAPCI
2078c2ecf20Sopenharmony_ci	int line;
2088c2ecf20Sopenharmony_ci	unsigned long base;
2098c2ecf20Sopenharmony_ci	struct uart_8250_port uart;
2108c2ecf20Sopenharmony_ci	struct hp300_port *port;
2118c2ecf20Sopenharmony_ci	int i;
2128c2ecf20Sopenharmony_ci#endif
2138c2ecf20Sopenharmony_ci	if (called)
2148c2ecf20Sopenharmony_ci		return -ENODEV;
2158c2ecf20Sopenharmony_ci	called = 1;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	if (!MACH_IS_HP300)
2188c2ecf20Sopenharmony_ci		return -ENODEV;
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci#ifdef CONFIG_HPDCA
2218c2ecf20Sopenharmony_ci	dio_register_driver(&hpdca_driver);
2228c2ecf20Sopenharmony_ci#endif
2238c2ecf20Sopenharmony_ci#ifdef CONFIG_HPAPCI
2248c2ecf20Sopenharmony_ci	if (hp300_model < HP_400) {
2258c2ecf20Sopenharmony_ci		if (!num_ports)
2268c2ecf20Sopenharmony_ci			return -ENODEV;
2278c2ecf20Sopenharmony_ci		return 0;
2288c2ecf20Sopenharmony_ci	}
2298c2ecf20Sopenharmony_ci	/* These models have the Frodo chip.
2308c2ecf20Sopenharmony_ci	 * Port 0 is reserved for the Apollo Domain keyboard.
2318c2ecf20Sopenharmony_ci	 * Port 1 is either the console or the DCA.
2328c2ecf20Sopenharmony_ci	 */
2338c2ecf20Sopenharmony_ci	for (i = 1; i < 4; i++) {
2348c2ecf20Sopenharmony_ci		/* Port 1 is the console on a 425e, on other machines it's
2358c2ecf20Sopenharmony_ci		 * mapped to DCA.
2368c2ecf20Sopenharmony_ci		 */
2378c2ecf20Sopenharmony_ci#ifdef CONFIG_SERIAL_8250_CONSOLE
2388c2ecf20Sopenharmony_ci		if (i == 1)
2398c2ecf20Sopenharmony_ci			continue;
2408c2ecf20Sopenharmony_ci#endif
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci		/* Create new serial device */
2438c2ecf20Sopenharmony_ci		port = kmalloc(sizeof(struct hp300_port), GFP_KERNEL);
2448c2ecf20Sopenharmony_ci		if (!port)
2458c2ecf20Sopenharmony_ci			return -ENOMEM;
2468c2ecf20Sopenharmony_ci
2478c2ecf20Sopenharmony_ci		memset(&uart, 0, sizeof(uart));
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci		base = (FRODO_BASE + FRODO_APCI_OFFSET(i));
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci		/* Memory mapped I/O */
2528c2ecf20Sopenharmony_ci		uart.port.iotype = UPIO_MEM;
2538c2ecf20Sopenharmony_ci		uart.port.flags = UPF_SKIP_TEST | UPF_SHARE_IRQ
2548c2ecf20Sopenharmony_ci				| UPF_BOOT_AUTOCONF;
2558c2ecf20Sopenharmony_ci		/* XXX - no interrupt support yet */
2568c2ecf20Sopenharmony_ci		uart.port.irq = 0;
2578c2ecf20Sopenharmony_ci		uart.port.uartclk = HPAPCI_BAUD_BASE * 16;
2588c2ecf20Sopenharmony_ci		uart.port.mapbase = base;
2598c2ecf20Sopenharmony_ci		uart.port.membase = (char *)(base + DIO_VIRADDRBASE);
2608c2ecf20Sopenharmony_ci		uart.port.regshift = 2;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci		line = serial8250_register_8250_port(&uart);
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci		if (line < 0) {
2658c2ecf20Sopenharmony_ci			dev_notice(uart.port.dev,
2668c2ecf20Sopenharmony_ci				   "8250_hp300: register_serial() APCI %d irq %d failed\n",
2678c2ecf20Sopenharmony_ci				   i, uart.port.irq);
2688c2ecf20Sopenharmony_ci			kfree(port);
2698c2ecf20Sopenharmony_ci			continue;
2708c2ecf20Sopenharmony_ci		}
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci		port->line = line;
2738c2ecf20Sopenharmony_ci		port->next = hp300_ports;
2748c2ecf20Sopenharmony_ci		hp300_ports = port;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci		num_ports++;
2778c2ecf20Sopenharmony_ci	}
2788c2ecf20Sopenharmony_ci#endif
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	/* Any boards found? */
2818c2ecf20Sopenharmony_ci	if (!num_ports)
2828c2ecf20Sopenharmony_ci		return -ENODEV;
2838c2ecf20Sopenharmony_ci
2848c2ecf20Sopenharmony_ci	return 0;
2858c2ecf20Sopenharmony_ci}
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci#ifdef CONFIG_HPDCA
2888c2ecf20Sopenharmony_cistatic void hpdca_remove_one(struct dio_dev *d)
2898c2ecf20Sopenharmony_ci{
2908c2ecf20Sopenharmony_ci	int line;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	line = (int) dio_get_drvdata(d);
2938c2ecf20Sopenharmony_ci	if (d->resource.start) {
2948c2ecf20Sopenharmony_ci		/* Disable board-interrupts */
2958c2ecf20Sopenharmony_ci		out_8(d->resource.start + DIO_VIRADDRBASE + DCA_IC, 0);
2968c2ecf20Sopenharmony_ci	}
2978c2ecf20Sopenharmony_ci	serial8250_unregister_port(line);
2988c2ecf20Sopenharmony_ci}
2998c2ecf20Sopenharmony_ci#endif
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_cistatic void __exit hp300_8250_exit(void)
3028c2ecf20Sopenharmony_ci{
3038c2ecf20Sopenharmony_ci#ifdef CONFIG_HPAPCI
3048c2ecf20Sopenharmony_ci	struct hp300_port *port, *to_free;
3058c2ecf20Sopenharmony_ci
3068c2ecf20Sopenharmony_ci	for (port = hp300_ports; port; ) {
3078c2ecf20Sopenharmony_ci		serial8250_unregister_port(port->line);
3088c2ecf20Sopenharmony_ci		to_free = port;
3098c2ecf20Sopenharmony_ci		port = port->next;
3108c2ecf20Sopenharmony_ci		kfree(to_free);
3118c2ecf20Sopenharmony_ci	}
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	hp300_ports = NULL;
3148c2ecf20Sopenharmony_ci#endif
3158c2ecf20Sopenharmony_ci#ifdef CONFIG_HPDCA
3168c2ecf20Sopenharmony_ci	dio_unregister_driver(&hpdca_driver);
3178c2ecf20Sopenharmony_ci#endif
3188c2ecf20Sopenharmony_ci}
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_cimodule_init(hp300_8250_init);
3218c2ecf20Sopenharmony_cimodule_exit(hp300_8250_exit);
3228c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HP DCA/APCI serial driver");
3238c2ecf20Sopenharmony_ciMODULE_AUTHOR("Kars de Jong <jongk@linux-m68k.org>");
3248c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
325