162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  Amstrad E3 (Delta) keyboard port driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (c) 2006 Matt Callow
662306a36Sopenharmony_ci *  Copyright (c) 2010 Janusz Krzysztofik
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Thanks to Cliff Lawson for his help
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * The Amstrad Delta keyboard (aka mailboard) uses normal PC-AT style serial
1162306a36Sopenharmony_ci * transmission.  The keyboard port is formed of two GPIO lines, for clock
1262306a36Sopenharmony_ci * and data.  Due to strict timing requirements of the interface,
1362306a36Sopenharmony_ci * the serial data stream is read and processed by a FIQ handler.
1462306a36Sopenharmony_ci * The resulting words are fetched by this driver from a circular buffer.
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * Standard AT keyboard driver (atkbd) is used for handling the keyboard data.
1762306a36Sopenharmony_ci * However, when used with the E3 mailboard that producecs non-standard
1862306a36Sopenharmony_ci * scancodes, a custom key table must be prepared and loaded from userspace.
1962306a36Sopenharmony_ci */
2062306a36Sopenharmony_ci#include <linux/irq.h>
2162306a36Sopenharmony_ci#include <linux/platform_data/ams-delta-fiq.h>
2262306a36Sopenharmony_ci#include <linux/platform_device.h>
2362306a36Sopenharmony_ci#include <linux/regulator/consumer.h>
2462306a36Sopenharmony_ci#include <linux/serio.h>
2562306a36Sopenharmony_ci#include <linux/slab.h>
2662306a36Sopenharmony_ci#include <linux/module.h>
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci#define DRIVER_NAME	"ams-delta-serio"
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ciMODULE_AUTHOR("Matt Callow");
3162306a36Sopenharmony_ciMODULE_DESCRIPTION("AMS Delta (E3) keyboard port driver");
3262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistruct ams_delta_serio {
3562306a36Sopenharmony_ci	struct serio *serio;
3662306a36Sopenharmony_ci	struct regulator *vcc;
3762306a36Sopenharmony_ci	unsigned int *fiq_buffer;
3862306a36Sopenharmony_ci};
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_cistatic int check_data(struct serio *serio, int data)
4162306a36Sopenharmony_ci{
4262306a36Sopenharmony_ci	int i, parity = 0;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	/* check valid stop bit */
4562306a36Sopenharmony_ci	if (!(data & 0x400)) {
4662306a36Sopenharmony_ci		dev_warn(&serio->dev, "invalid stop bit, data=0x%X\n", data);
4762306a36Sopenharmony_ci		return SERIO_FRAME;
4862306a36Sopenharmony_ci	}
4962306a36Sopenharmony_ci	/* calculate the parity */
5062306a36Sopenharmony_ci	for (i = 1; i < 10; i++) {
5162306a36Sopenharmony_ci		if (data & (1 << i))
5262306a36Sopenharmony_ci			parity++;
5362306a36Sopenharmony_ci	}
5462306a36Sopenharmony_ci	/* it should be odd */
5562306a36Sopenharmony_ci	if (!(parity & 0x01)) {
5662306a36Sopenharmony_ci		dev_warn(&serio->dev,
5762306a36Sopenharmony_ci			 "parity check failed, data=0x%X parity=0x%X\n", data,
5862306a36Sopenharmony_ci			 parity);
5962306a36Sopenharmony_ci		return SERIO_PARITY;
6062306a36Sopenharmony_ci	}
6162306a36Sopenharmony_ci	return 0;
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic irqreturn_t ams_delta_serio_interrupt(int irq, void *dev_id)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	struct ams_delta_serio *priv = dev_id;
6762306a36Sopenharmony_ci	int *circ_buff = &priv->fiq_buffer[FIQ_CIRC_BUFF];
6862306a36Sopenharmony_ci	int data, dfl;
6962306a36Sopenharmony_ci	u8 scancode;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	priv->fiq_buffer[FIQ_IRQ_PEND] = 0;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/*
7462306a36Sopenharmony_ci	 * Read data from the circular buffer, check it
7562306a36Sopenharmony_ci	 * and then pass it on the serio
7662306a36Sopenharmony_ci	 */
7762306a36Sopenharmony_ci	while (priv->fiq_buffer[FIQ_KEYS_CNT] > 0) {
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci		data = circ_buff[priv->fiq_buffer[FIQ_HEAD_OFFSET]++];
8062306a36Sopenharmony_ci		priv->fiq_buffer[FIQ_KEYS_CNT]--;
8162306a36Sopenharmony_ci		if (priv->fiq_buffer[FIQ_HEAD_OFFSET] ==
8262306a36Sopenharmony_ci		    priv->fiq_buffer[FIQ_BUF_LEN])
8362306a36Sopenharmony_ci			priv->fiq_buffer[FIQ_HEAD_OFFSET] = 0;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci		dfl = check_data(priv->serio, data);
8662306a36Sopenharmony_ci		scancode = (u8) (data >> 1) & 0xFF;
8762306a36Sopenharmony_ci		serio_interrupt(priv->serio, scancode, dfl);
8862306a36Sopenharmony_ci	}
8962306a36Sopenharmony_ci	return IRQ_HANDLED;
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic int ams_delta_serio_open(struct serio *serio)
9362306a36Sopenharmony_ci{
9462306a36Sopenharmony_ci	struct ams_delta_serio *priv = serio->port_data;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/* enable keyboard */
9762306a36Sopenharmony_ci	return regulator_enable(priv->vcc);
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_cistatic void ams_delta_serio_close(struct serio *serio)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	struct ams_delta_serio *priv = serio->port_data;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* disable keyboard */
10562306a36Sopenharmony_ci	regulator_disable(priv->vcc);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic int ams_delta_serio_init(struct platform_device *pdev)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct ams_delta_serio *priv;
11162306a36Sopenharmony_ci	struct serio *serio;
11262306a36Sopenharmony_ci	int irq, err;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
11562306a36Sopenharmony_ci	if (!priv)
11662306a36Sopenharmony_ci		return -ENOMEM;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	priv->fiq_buffer = pdev->dev.platform_data;
11962306a36Sopenharmony_ci	if (!priv->fiq_buffer)
12062306a36Sopenharmony_ci		return -EINVAL;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	priv->vcc = devm_regulator_get(&pdev->dev, "vcc");
12362306a36Sopenharmony_ci	if (IS_ERR(priv->vcc)) {
12462306a36Sopenharmony_ci		err = PTR_ERR(priv->vcc);
12562306a36Sopenharmony_ci		dev_err(&pdev->dev, "regulator request failed (%d)\n", err);
12662306a36Sopenharmony_ci		/*
12762306a36Sopenharmony_ci		 * When running on a non-dt platform and requested regulator
12862306a36Sopenharmony_ci		 * is not available, devm_regulator_get() never returns
12962306a36Sopenharmony_ci		 * -EPROBE_DEFER as it is not able to justify if the regulator
13062306a36Sopenharmony_ci		 * may still appear later.  On the other hand, the board can
13162306a36Sopenharmony_ci		 * still set full constriants flag at late_initcall in order
13262306a36Sopenharmony_ci		 * to instruct devm_regulator_get() to returnn a dummy one
13362306a36Sopenharmony_ci		 * if sufficient.  Hence, if we get -ENODEV here, let's convert
13462306a36Sopenharmony_ci		 * it to -EPROBE_DEFER and wait for the board to decide or
13562306a36Sopenharmony_ci		 * let Deferred Probe infrastructure handle this error.
13662306a36Sopenharmony_ci		 */
13762306a36Sopenharmony_ci		if (err == -ENODEV)
13862306a36Sopenharmony_ci			err = -EPROBE_DEFER;
13962306a36Sopenharmony_ci		return err;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
14362306a36Sopenharmony_ci	if (irq < 0)
14462306a36Sopenharmony_ci		return -ENXIO;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	err = devm_request_irq(&pdev->dev, irq, ams_delta_serio_interrupt,
14762306a36Sopenharmony_ci			       IRQ_TYPE_EDGE_RISING, DRIVER_NAME, priv);
14862306a36Sopenharmony_ci	if (err < 0) {
14962306a36Sopenharmony_ci		dev_err(&pdev->dev, "IRQ request failed (%d)\n", err);
15062306a36Sopenharmony_ci		return err;
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	serio = kzalloc(sizeof(*serio), GFP_KERNEL);
15462306a36Sopenharmony_ci	if (!serio)
15562306a36Sopenharmony_ci		return -ENOMEM;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	priv->serio = serio;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	serio->id.type = SERIO_8042;
16062306a36Sopenharmony_ci	serio->open = ams_delta_serio_open;
16162306a36Sopenharmony_ci	serio->close = ams_delta_serio_close;
16262306a36Sopenharmony_ci	strscpy(serio->name, "AMS DELTA keyboard adapter", sizeof(serio->name));
16362306a36Sopenharmony_ci	strscpy(serio->phys, dev_name(&pdev->dev), sizeof(serio->phys));
16462306a36Sopenharmony_ci	serio->dev.parent = &pdev->dev;
16562306a36Sopenharmony_ci	serio->port_data = priv;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	serio_register_port(serio);
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	platform_set_drvdata(pdev, priv);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	dev_info(&serio->dev, "%s\n", serio->name);
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	return 0;
17462306a36Sopenharmony_ci}
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cistatic int ams_delta_serio_exit(struct platform_device *pdev)
17762306a36Sopenharmony_ci{
17862306a36Sopenharmony_ci	struct ams_delta_serio *priv = platform_get_drvdata(pdev);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	serio_unregister_port(priv->serio);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	return 0;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic struct platform_driver ams_delta_serio_driver = {
18662306a36Sopenharmony_ci	.probe	= ams_delta_serio_init,
18762306a36Sopenharmony_ci	.remove	= ams_delta_serio_exit,
18862306a36Sopenharmony_ci	.driver	= {
18962306a36Sopenharmony_ci		.name	= DRIVER_NAME
19062306a36Sopenharmony_ci	},
19162306a36Sopenharmony_ci};
19262306a36Sopenharmony_cimodule_platform_driver(ams_delta_serio_driver);
193