18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Altera University Program PS2 controller driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2008 Thomas Chou <thomas@wytron.com.tw>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Based on sa1111ps2.c, which is:
88c2ecf20Sopenharmony_ci * Copyright (C) 2002 Russell King
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/module.h>
128c2ecf20Sopenharmony_ci#include <linux/input.h>
138c2ecf20Sopenharmony_ci#include <linux/serio.h>
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
168c2ecf20Sopenharmony_ci#include <linux/io.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/of.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define DRV_NAME "altera_ps2"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_cistruct ps2if {
238c2ecf20Sopenharmony_ci	struct serio *io;
248c2ecf20Sopenharmony_ci	void __iomem *base;
258c2ecf20Sopenharmony_ci};
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci/*
288c2ecf20Sopenharmony_ci * Read all bytes waiting in the PS2 port.  There should be
298c2ecf20Sopenharmony_ci * at the most one, but we loop for safety.
308c2ecf20Sopenharmony_ci */
318c2ecf20Sopenharmony_cistatic irqreturn_t altera_ps2_rxint(int irq, void *dev_id)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	struct ps2if *ps2if = dev_id;
348c2ecf20Sopenharmony_ci	unsigned int status;
358c2ecf20Sopenharmony_ci	irqreturn_t handled = IRQ_NONE;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	while ((status = readl(ps2if->base)) & 0xffff0000) {
388c2ecf20Sopenharmony_ci		serio_interrupt(ps2if->io, status & 0xff, 0);
398c2ecf20Sopenharmony_ci		handled = IRQ_HANDLED;
408c2ecf20Sopenharmony_ci	}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	return handled;
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/*
468c2ecf20Sopenharmony_ci * Write a byte to the PS2 port.
478c2ecf20Sopenharmony_ci */
488c2ecf20Sopenharmony_cistatic int altera_ps2_write(struct serio *io, unsigned char val)
498c2ecf20Sopenharmony_ci{
508c2ecf20Sopenharmony_ci	struct ps2if *ps2if = io->port_data;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	writel(val, ps2if->base);
538c2ecf20Sopenharmony_ci	return 0;
548c2ecf20Sopenharmony_ci}
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_cistatic int altera_ps2_open(struct serio *io)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	struct ps2if *ps2if = io->port_data;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	/* clear fifo */
618c2ecf20Sopenharmony_ci	while (readl(ps2if->base) & 0xffff0000)
628c2ecf20Sopenharmony_ci		/* empty */;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	writel(1, ps2if->base + 4); /* enable rx irq */
658c2ecf20Sopenharmony_ci	return 0;
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic void altera_ps2_close(struct serio *io)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	struct ps2if *ps2if = io->port_data;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	writel(0, ps2if->base + 4); /* disable rx irq */
738c2ecf20Sopenharmony_ci}
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci/*
768c2ecf20Sopenharmony_ci * Add one device to this driver.
778c2ecf20Sopenharmony_ci */
788c2ecf20Sopenharmony_cistatic int altera_ps2_probe(struct platform_device *pdev)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	struct ps2if *ps2if;
818c2ecf20Sopenharmony_ci	struct resource *res;
828c2ecf20Sopenharmony_ci	struct serio *serio;
838c2ecf20Sopenharmony_ci	int error, irq;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	ps2if = devm_kzalloc(&pdev->dev, sizeof(struct ps2if), GFP_KERNEL);
868c2ecf20Sopenharmony_ci	if (!ps2if)
878c2ecf20Sopenharmony_ci		return -ENOMEM;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
908c2ecf20Sopenharmony_ci	ps2if->base = devm_ioremap_resource(&pdev->dev, res);
918c2ecf20Sopenharmony_ci	if (IS_ERR(ps2if->base))
928c2ecf20Sopenharmony_ci		return PTR_ERR(ps2if->base);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
958c2ecf20Sopenharmony_ci	if (irq < 0)
968c2ecf20Sopenharmony_ci		return -ENXIO;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	error = devm_request_irq(&pdev->dev, irq, altera_ps2_rxint, 0,
998c2ecf20Sopenharmony_ci				 pdev->name, ps2if);
1008c2ecf20Sopenharmony_ci	if (error) {
1018c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "could not request IRQ %d\n", irq);
1028c2ecf20Sopenharmony_ci		return error;
1038c2ecf20Sopenharmony_ci	}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
1068c2ecf20Sopenharmony_ci	if (!serio)
1078c2ecf20Sopenharmony_ci		return -ENOMEM;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	serio->id.type		= SERIO_8042;
1108c2ecf20Sopenharmony_ci	serio->write		= altera_ps2_write;
1118c2ecf20Sopenharmony_ci	serio->open		= altera_ps2_open;
1128c2ecf20Sopenharmony_ci	serio->close		= altera_ps2_close;
1138c2ecf20Sopenharmony_ci	strlcpy(serio->name, dev_name(&pdev->dev), sizeof(serio->name));
1148c2ecf20Sopenharmony_ci	strlcpy(serio->phys, dev_name(&pdev->dev), sizeof(serio->phys));
1158c2ecf20Sopenharmony_ci	serio->port_data	= ps2if;
1168c2ecf20Sopenharmony_ci	serio->dev.parent	= &pdev->dev;
1178c2ecf20Sopenharmony_ci	ps2if->io		= serio;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	dev_info(&pdev->dev, "base %p, irq %d\n", ps2if->base, irq);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	serio_register_port(ps2if->io);
1228c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, ps2if);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	return 0;
1258c2ecf20Sopenharmony_ci}
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci/*
1288c2ecf20Sopenharmony_ci * Remove one device from this driver.
1298c2ecf20Sopenharmony_ci */
1308c2ecf20Sopenharmony_cistatic int altera_ps2_remove(struct platform_device *pdev)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	struct ps2if *ps2if = platform_get_drvdata(pdev);
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	serio_unregister_port(ps2if->io);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	return 0;
1378c2ecf20Sopenharmony_ci}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci#ifdef CONFIG_OF
1408c2ecf20Sopenharmony_cistatic const struct of_device_id altera_ps2_match[] = {
1418c2ecf20Sopenharmony_ci	{ .compatible = "ALTR,ps2-1.0", },
1428c2ecf20Sopenharmony_ci	{ .compatible = "altr,ps2-1.0", },
1438c2ecf20Sopenharmony_ci	{},
1448c2ecf20Sopenharmony_ci};
1458c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, altera_ps2_match);
1468c2ecf20Sopenharmony_ci#endif /* CONFIG_OF */
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci/*
1498c2ecf20Sopenharmony_ci * Our device driver structure
1508c2ecf20Sopenharmony_ci */
1518c2ecf20Sopenharmony_cistatic struct platform_driver altera_ps2_driver = {
1528c2ecf20Sopenharmony_ci	.probe		= altera_ps2_probe,
1538c2ecf20Sopenharmony_ci	.remove		= altera_ps2_remove,
1548c2ecf20Sopenharmony_ci	.driver	= {
1558c2ecf20Sopenharmony_ci		.name	= DRV_NAME,
1568c2ecf20Sopenharmony_ci		.of_match_table = of_match_ptr(altera_ps2_match),
1578c2ecf20Sopenharmony_ci	},
1588c2ecf20Sopenharmony_ci};
1598c2ecf20Sopenharmony_cimodule_platform_driver(altera_ps2_driver);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Altera University Program PS2 controller driver");
1628c2ecf20Sopenharmony_ciMODULE_AUTHOR("Thomas Chou <thomas@wytron.com.tw>");
1638c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1648c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:" DRV_NAME);
165