18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com)
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Driver is originally developed by Pavel Sokolov <psokolov@synopsys.com>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/err.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
118c2ecf20Sopenharmony_ci#include <linux/input.h>
128c2ecf20Sopenharmony_ci#include <linux/serio.h>
138c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
148c2ecf20Sopenharmony_ci#include <linux/of.h>
158c2ecf20Sopenharmony_ci#include <linux/io.h>
168c2ecf20Sopenharmony_ci#include <linux/kernel.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define ARC_PS2_PORTS                   2
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define ARC_ARC_PS2_ID                  0x0001f609
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define STAT_TIMEOUT                    128
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define PS2_STAT_RX_FRM_ERR             (1)
268c2ecf20Sopenharmony_ci#define PS2_STAT_RX_BUF_OVER            (1 << 1)
278c2ecf20Sopenharmony_ci#define PS2_STAT_RX_INT_EN              (1 << 2)
288c2ecf20Sopenharmony_ci#define PS2_STAT_RX_VAL                 (1 << 3)
298c2ecf20Sopenharmony_ci#define PS2_STAT_TX_ISNOT_FUL           (1 << 4)
308c2ecf20Sopenharmony_ci#define PS2_STAT_TX_INT_EN              (1 << 5)
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistruct arc_ps2_port {
338c2ecf20Sopenharmony_ci	void __iomem *data_addr;
348c2ecf20Sopenharmony_ci	void __iomem *status_addr;
358c2ecf20Sopenharmony_ci	struct serio *io;
368c2ecf20Sopenharmony_ci};
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistruct arc_ps2_data {
398c2ecf20Sopenharmony_ci	struct arc_ps2_port port[ARC_PS2_PORTS];
408c2ecf20Sopenharmony_ci	void __iomem *addr;
418c2ecf20Sopenharmony_ci	unsigned int frame_error;
428c2ecf20Sopenharmony_ci	unsigned int buf_overflow;
438c2ecf20Sopenharmony_ci	unsigned int total_int;
448c2ecf20Sopenharmony_ci};
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic void arc_ps2_check_rx(struct arc_ps2_data *arc_ps2,
478c2ecf20Sopenharmony_ci			     struct arc_ps2_port *port)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	unsigned int timeout = 1000;
508c2ecf20Sopenharmony_ci	unsigned int flag, status;
518c2ecf20Sopenharmony_ci	unsigned char data;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_ci	do {
548c2ecf20Sopenharmony_ci		status = ioread32(port->status_addr);
558c2ecf20Sopenharmony_ci		if (!(status & PS2_STAT_RX_VAL))
568c2ecf20Sopenharmony_ci			return;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci		data = ioread32(port->data_addr) & 0xff;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci		flag = 0;
618c2ecf20Sopenharmony_ci		arc_ps2->total_int++;
628c2ecf20Sopenharmony_ci		if (status & PS2_STAT_RX_FRM_ERR) {
638c2ecf20Sopenharmony_ci			arc_ps2->frame_error++;
648c2ecf20Sopenharmony_ci			flag |= SERIO_PARITY;
658c2ecf20Sopenharmony_ci		} else if (status & PS2_STAT_RX_BUF_OVER) {
668c2ecf20Sopenharmony_ci			arc_ps2->buf_overflow++;
678c2ecf20Sopenharmony_ci			flag |= SERIO_FRAME;
688c2ecf20Sopenharmony_ci		}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci		serio_interrupt(port->io, data, flag);
718c2ecf20Sopenharmony_ci	} while (--timeout);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	dev_err(&port->io->dev, "PS/2 hardware stuck\n");
748c2ecf20Sopenharmony_ci}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_cistatic irqreturn_t arc_ps2_interrupt(int irq, void *dev)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	struct arc_ps2_data *arc_ps2 = dev;
798c2ecf20Sopenharmony_ci	int i;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	for (i = 0; i < ARC_PS2_PORTS; i++)
828c2ecf20Sopenharmony_ci		arc_ps2_check_rx(arc_ps2, &arc_ps2->port[i]);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic int arc_ps2_write(struct serio *io, unsigned char val)
888c2ecf20Sopenharmony_ci{
898c2ecf20Sopenharmony_ci	unsigned status;
908c2ecf20Sopenharmony_ci	struct arc_ps2_port *port = io->port_data;
918c2ecf20Sopenharmony_ci	int timeout = STAT_TIMEOUT;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	do {
948c2ecf20Sopenharmony_ci		status = ioread32(port->status_addr);
958c2ecf20Sopenharmony_ci		cpu_relax();
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci		if (status & PS2_STAT_TX_ISNOT_FUL) {
988c2ecf20Sopenharmony_ci			iowrite32(val & 0xff, port->data_addr);
998c2ecf20Sopenharmony_ci			return 0;
1008c2ecf20Sopenharmony_ci		}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	} while (--timeout);
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	dev_err(&io->dev, "write timeout\n");
1058c2ecf20Sopenharmony_ci	return -ETIMEDOUT;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_cistatic int arc_ps2_open(struct serio *io)
1098c2ecf20Sopenharmony_ci{
1108c2ecf20Sopenharmony_ci	struct arc_ps2_port *port = io->port_data;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	iowrite32(PS2_STAT_RX_INT_EN, port->status_addr);
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	return 0;
1158c2ecf20Sopenharmony_ci}
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_cistatic void arc_ps2_close(struct serio *io)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	struct arc_ps2_port *port = io->port_data;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	iowrite32(ioread32(port->status_addr) & ~PS2_STAT_RX_INT_EN,
1228c2ecf20Sopenharmony_ci		  port->status_addr);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic void __iomem *arc_ps2_calc_addr(struct arc_ps2_data *arc_ps2,
1268c2ecf20Sopenharmony_ci						  int index, bool status)
1278c2ecf20Sopenharmony_ci{
1288c2ecf20Sopenharmony_ci	void __iomem *addr;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	addr = arc_ps2->addr + 4 + 4 * index;
1318c2ecf20Sopenharmony_ci	if (status)
1328c2ecf20Sopenharmony_ci		addr += ARC_PS2_PORTS * 4;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	return addr;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic void arc_ps2_inhibit_ports(struct arc_ps2_data *arc_ps2)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	void __iomem *addr;
1408c2ecf20Sopenharmony_ci	u32 val;
1418c2ecf20Sopenharmony_ci	int i;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	for (i = 0; i < ARC_PS2_PORTS; i++) {
1448c2ecf20Sopenharmony_ci		addr = arc_ps2_calc_addr(arc_ps2, i, true);
1458c2ecf20Sopenharmony_ci		val = ioread32(addr);
1468c2ecf20Sopenharmony_ci		val &= ~(PS2_STAT_RX_INT_EN | PS2_STAT_TX_INT_EN);
1478c2ecf20Sopenharmony_ci		iowrite32(val, addr);
1488c2ecf20Sopenharmony_ci	}
1498c2ecf20Sopenharmony_ci}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_cistatic int arc_ps2_create_port(struct platform_device *pdev,
1528c2ecf20Sopenharmony_ci					 struct arc_ps2_data *arc_ps2,
1538c2ecf20Sopenharmony_ci					 int index)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	struct arc_ps2_port *port = &arc_ps2->port[index];
1568c2ecf20Sopenharmony_ci	struct serio *io;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	io = kzalloc(sizeof(struct serio), GFP_KERNEL);
1598c2ecf20Sopenharmony_ci	if (!io)
1608c2ecf20Sopenharmony_ci		return -ENOMEM;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	io->id.type = SERIO_8042;
1638c2ecf20Sopenharmony_ci	io->write = arc_ps2_write;
1648c2ecf20Sopenharmony_ci	io->open = arc_ps2_open;
1658c2ecf20Sopenharmony_ci	io->close = arc_ps2_close;
1668c2ecf20Sopenharmony_ci	snprintf(io->name, sizeof(io->name), "ARC PS/2 port%d", index);
1678c2ecf20Sopenharmony_ci	snprintf(io->phys, sizeof(io->phys), "arc/serio%d", index);
1688c2ecf20Sopenharmony_ci	io->port_data = port;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	port->io = io;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	port->data_addr = arc_ps2_calc_addr(arc_ps2, index, false);
1738c2ecf20Sopenharmony_ci	port->status_addr = arc_ps2_calc_addr(arc_ps2, index, true);
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	dev_dbg(&pdev->dev, "port%d is allocated (data = 0x%p, status = 0x%p)\n",
1768c2ecf20Sopenharmony_ci		index, port->data_addr, port->status_addr);
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	serio_register_port(port->io);
1798c2ecf20Sopenharmony_ci	return 0;
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic int arc_ps2_probe(struct platform_device *pdev)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct arc_ps2_data *arc_ps2;
1858c2ecf20Sopenharmony_ci	struct resource *res;
1868c2ecf20Sopenharmony_ci	int irq;
1878c2ecf20Sopenharmony_ci	int error, id, i;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	irq = platform_get_irq_byname(pdev, "arc_ps2_irq");
1908c2ecf20Sopenharmony_ci	if (irq < 0)
1918c2ecf20Sopenharmony_ci		return -EINVAL;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	arc_ps2 = devm_kzalloc(&pdev->dev, sizeof(struct arc_ps2_data),
1948c2ecf20Sopenharmony_ci				GFP_KERNEL);
1958c2ecf20Sopenharmony_ci	if (!arc_ps2) {
1968c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "out of memory\n");
1978c2ecf20Sopenharmony_ci		return -ENOMEM;
1988c2ecf20Sopenharmony_ci	}
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
2018c2ecf20Sopenharmony_ci	arc_ps2->addr = devm_ioremap_resource(&pdev->dev, res);
2028c2ecf20Sopenharmony_ci	if (IS_ERR(arc_ps2->addr))
2038c2ecf20Sopenharmony_ci		return PTR_ERR(arc_ps2->addr);
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	dev_info(&pdev->dev, "irq = %d, address = 0x%p, ports = %i\n",
2068c2ecf20Sopenharmony_ci		 irq, arc_ps2->addr, ARC_PS2_PORTS);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	id = ioread32(arc_ps2->addr);
2098c2ecf20Sopenharmony_ci	if (id != ARC_ARC_PS2_ID) {
2108c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "device id does not match\n");
2118c2ecf20Sopenharmony_ci		return -ENXIO;
2128c2ecf20Sopenharmony_ci	}
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	arc_ps2_inhibit_ports(arc_ps2);
2158c2ecf20Sopenharmony_ci
2168c2ecf20Sopenharmony_ci	error = devm_request_irq(&pdev->dev, irq, arc_ps2_interrupt,
2178c2ecf20Sopenharmony_ci				 0, "arc_ps2", arc_ps2);
2188c2ecf20Sopenharmony_ci	if (error) {
2198c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Could not allocate IRQ\n");
2208c2ecf20Sopenharmony_ci		return error;
2218c2ecf20Sopenharmony_ci	}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	for (i = 0; i < ARC_PS2_PORTS; i++) {
2248c2ecf20Sopenharmony_ci		error = arc_ps2_create_port(pdev, arc_ps2, i);
2258c2ecf20Sopenharmony_ci		if (error) {
2268c2ecf20Sopenharmony_ci			while (--i >= 0)
2278c2ecf20Sopenharmony_ci				serio_unregister_port(arc_ps2->port[i].io);
2288c2ecf20Sopenharmony_ci			return error;
2298c2ecf20Sopenharmony_ci		}
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, arc_ps2);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci	return 0;
2358c2ecf20Sopenharmony_ci}
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_cistatic int arc_ps2_remove(struct platform_device *pdev)
2388c2ecf20Sopenharmony_ci{
2398c2ecf20Sopenharmony_ci	struct arc_ps2_data *arc_ps2 = platform_get_drvdata(pdev);
2408c2ecf20Sopenharmony_ci	int i;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	for (i = 0; i < ARC_PS2_PORTS; i++)
2438c2ecf20Sopenharmony_ci		serio_unregister_port(arc_ps2->port[i].io);
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	dev_dbg(&pdev->dev, "interrupt count = %i\n", arc_ps2->total_int);
2468c2ecf20Sopenharmony_ci	dev_dbg(&pdev->dev, "frame error count = %i\n", arc_ps2->frame_error);
2478c2ecf20Sopenharmony_ci	dev_dbg(&pdev->dev, "buffer overflow count = %i\n",
2488c2ecf20Sopenharmony_ci		arc_ps2->buf_overflow);
2498c2ecf20Sopenharmony_ci
2508c2ecf20Sopenharmony_ci	return 0;
2518c2ecf20Sopenharmony_ci}
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
2548c2ecf20Sopenharmony_cistatic const struct of_device_id arc_ps2_match[] = {
2558c2ecf20Sopenharmony_ci	{ .compatible = "snps,arc_ps2" },
2568c2ecf20Sopenharmony_ci	{ },
2578c2ecf20Sopenharmony_ci};
2588c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, arc_ps2_match);
2598c2ecf20Sopenharmony_ci#endif
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic struct platform_driver arc_ps2_driver = {
2628c2ecf20Sopenharmony_ci	.driver	= {
2638c2ecf20Sopenharmony_ci		.name		= "arc_ps2",
2648c2ecf20Sopenharmony_ci		.of_match_table	= of_match_ptr(arc_ps2_match),
2658c2ecf20Sopenharmony_ci	},
2668c2ecf20Sopenharmony_ci	.probe	= arc_ps2_probe,
2678c2ecf20Sopenharmony_ci	.remove	= arc_ps2_remove,
2688c2ecf20Sopenharmony_ci};
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_cimodule_platform_driver(arc_ps2_driver);
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2738c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pavel Sokolov <psokolov@synopsys.com>");
2748c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ARC PS/2 Driver");
275