18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Amstrad E3 (Delta) keyboard port driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (c) 2006 Matt Callow 68c2ecf20Sopenharmony_ci * Copyright (c) 2010 Janusz Krzysztofik 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Thanks to Cliff Lawson for his help 98c2ecf20Sopenharmony_ci * 108c2ecf20Sopenharmony_ci * The Amstrad Delta keyboard (aka mailboard) uses normal PC-AT style serial 118c2ecf20Sopenharmony_ci * transmission. The keyboard port is formed of two GPIO lines, for clock 128c2ecf20Sopenharmony_ci * and data. Due to strict timing requirements of the interface, 138c2ecf20Sopenharmony_ci * the serial data stream is read and processed by a FIQ handler. 148c2ecf20Sopenharmony_ci * The resulting words are fetched by this driver from a circular buffer. 158c2ecf20Sopenharmony_ci * 168c2ecf20Sopenharmony_ci * Standard AT keyboard driver (atkbd) is used for handling the keyboard data. 178c2ecf20Sopenharmony_ci * However, when used with the E3 mailboard that producecs non-standard 188c2ecf20Sopenharmony_ci * scancodes, a custom key table must be prepared and loaded from userspace. 198c2ecf20Sopenharmony_ci */ 208c2ecf20Sopenharmony_ci#include <linux/irq.h> 218c2ecf20Sopenharmony_ci#include <linux/platform_data/ams-delta-fiq.h> 228c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 238c2ecf20Sopenharmony_ci#include <linux/regulator/consumer.h> 248c2ecf20Sopenharmony_ci#include <linux/serio.h> 258c2ecf20Sopenharmony_ci#include <linux/slab.h> 268c2ecf20Sopenharmony_ci#include <linux/module.h> 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci#define DRIVER_NAME "ams-delta-serio" 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ciMODULE_AUTHOR("Matt Callow"); 318c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("AMS Delta (E3) keyboard port driver"); 328c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistruct ams_delta_serio { 358c2ecf20Sopenharmony_ci struct serio *serio; 368c2ecf20Sopenharmony_ci struct regulator *vcc; 378c2ecf20Sopenharmony_ci unsigned int *fiq_buffer; 388c2ecf20Sopenharmony_ci}; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic int check_data(struct serio *serio, int data) 418c2ecf20Sopenharmony_ci{ 428c2ecf20Sopenharmony_ci int i, parity = 0; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_ci /* check valid stop bit */ 458c2ecf20Sopenharmony_ci if (!(data & 0x400)) { 468c2ecf20Sopenharmony_ci dev_warn(&serio->dev, "invalid stop bit, data=0x%X\n", data); 478c2ecf20Sopenharmony_ci return SERIO_FRAME; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci /* calculate the parity */ 508c2ecf20Sopenharmony_ci for (i = 1; i < 10; i++) { 518c2ecf20Sopenharmony_ci if (data & (1 << i)) 528c2ecf20Sopenharmony_ci parity++; 538c2ecf20Sopenharmony_ci } 548c2ecf20Sopenharmony_ci /* it should be odd */ 558c2ecf20Sopenharmony_ci if (!(parity & 0x01)) { 568c2ecf20Sopenharmony_ci dev_warn(&serio->dev, 578c2ecf20Sopenharmony_ci "parity check failed, data=0x%X parity=0x%X\n", data, 588c2ecf20Sopenharmony_ci parity); 598c2ecf20Sopenharmony_ci return SERIO_PARITY; 608c2ecf20Sopenharmony_ci } 618c2ecf20Sopenharmony_ci return 0; 628c2ecf20Sopenharmony_ci} 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_cistatic irqreturn_t ams_delta_serio_interrupt(int irq, void *dev_id) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct ams_delta_serio *priv = dev_id; 678c2ecf20Sopenharmony_ci int *circ_buff = &priv->fiq_buffer[FIQ_CIRC_BUFF]; 688c2ecf20Sopenharmony_ci int data, dfl; 698c2ecf20Sopenharmony_ci u8 scancode; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci priv->fiq_buffer[FIQ_IRQ_PEND] = 0; 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci /* 748c2ecf20Sopenharmony_ci * Read data from the circular buffer, check it 758c2ecf20Sopenharmony_ci * and then pass it on the serio 768c2ecf20Sopenharmony_ci */ 778c2ecf20Sopenharmony_ci while (priv->fiq_buffer[FIQ_KEYS_CNT] > 0) { 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci data = circ_buff[priv->fiq_buffer[FIQ_HEAD_OFFSET]++]; 808c2ecf20Sopenharmony_ci priv->fiq_buffer[FIQ_KEYS_CNT]--; 818c2ecf20Sopenharmony_ci if (priv->fiq_buffer[FIQ_HEAD_OFFSET] == 828c2ecf20Sopenharmony_ci priv->fiq_buffer[FIQ_BUF_LEN]) 838c2ecf20Sopenharmony_ci priv->fiq_buffer[FIQ_HEAD_OFFSET] = 0; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci dfl = check_data(priv->serio, data); 868c2ecf20Sopenharmony_ci scancode = (u8) (data >> 1) & 0xFF; 878c2ecf20Sopenharmony_ci serio_interrupt(priv->serio, scancode, dfl); 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci return IRQ_HANDLED; 908c2ecf20Sopenharmony_ci} 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_cistatic int ams_delta_serio_open(struct serio *serio) 938c2ecf20Sopenharmony_ci{ 948c2ecf20Sopenharmony_ci struct ams_delta_serio *priv = serio->port_data; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci /* enable keyboard */ 978c2ecf20Sopenharmony_ci return regulator_enable(priv->vcc); 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic void ams_delta_serio_close(struct serio *serio) 1018c2ecf20Sopenharmony_ci{ 1028c2ecf20Sopenharmony_ci struct ams_delta_serio *priv = serio->port_data; 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* disable keyboard */ 1058c2ecf20Sopenharmony_ci regulator_disable(priv->vcc); 1068c2ecf20Sopenharmony_ci} 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic int ams_delta_serio_init(struct platform_device *pdev) 1098c2ecf20Sopenharmony_ci{ 1108c2ecf20Sopenharmony_ci struct ams_delta_serio *priv; 1118c2ecf20Sopenharmony_ci struct serio *serio; 1128c2ecf20Sopenharmony_ci int irq, err; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 1158c2ecf20Sopenharmony_ci if (!priv) 1168c2ecf20Sopenharmony_ci return -ENOMEM; 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci priv->fiq_buffer = pdev->dev.platform_data; 1198c2ecf20Sopenharmony_ci if (!priv->fiq_buffer) 1208c2ecf20Sopenharmony_ci return -EINVAL; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci priv->vcc = devm_regulator_get(&pdev->dev, "vcc"); 1238c2ecf20Sopenharmony_ci if (IS_ERR(priv->vcc)) { 1248c2ecf20Sopenharmony_ci err = PTR_ERR(priv->vcc); 1258c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "regulator request failed (%d)\n", err); 1268c2ecf20Sopenharmony_ci /* 1278c2ecf20Sopenharmony_ci * When running on a non-dt platform and requested regulator 1288c2ecf20Sopenharmony_ci * is not available, devm_regulator_get() never returns 1298c2ecf20Sopenharmony_ci * -EPROBE_DEFER as it is not able to justify if the regulator 1308c2ecf20Sopenharmony_ci * may still appear later. On the other hand, the board can 1318c2ecf20Sopenharmony_ci * still set full constriants flag at late_initcall in order 1328c2ecf20Sopenharmony_ci * to instruct devm_regulator_get() to returnn a dummy one 1338c2ecf20Sopenharmony_ci * if sufficient. Hence, if we get -ENODEV here, let's convert 1348c2ecf20Sopenharmony_ci * it to -EPROBE_DEFER and wait for the board to decide or 1358c2ecf20Sopenharmony_ci * let Deferred Probe infrastructure handle this error. 1368c2ecf20Sopenharmony_ci */ 1378c2ecf20Sopenharmony_ci if (err == -ENODEV) 1388c2ecf20Sopenharmony_ci err = -EPROBE_DEFER; 1398c2ecf20Sopenharmony_ci return err; 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci irq = platform_get_irq(pdev, 0); 1438c2ecf20Sopenharmony_ci if (irq < 0) 1448c2ecf20Sopenharmony_ci return -ENXIO; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci err = devm_request_irq(&pdev->dev, irq, ams_delta_serio_interrupt, 1478c2ecf20Sopenharmony_ci IRQ_TYPE_EDGE_RISING, DRIVER_NAME, priv); 1488c2ecf20Sopenharmony_ci if (err < 0) { 1498c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "IRQ request failed (%d)\n", err); 1508c2ecf20Sopenharmony_ci return err; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci serio = kzalloc(sizeof(*serio), GFP_KERNEL); 1548c2ecf20Sopenharmony_ci if (!serio) 1558c2ecf20Sopenharmony_ci return -ENOMEM; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_ci priv->serio = serio; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci serio->id.type = SERIO_8042; 1608c2ecf20Sopenharmony_ci serio->open = ams_delta_serio_open; 1618c2ecf20Sopenharmony_ci serio->close = ams_delta_serio_close; 1628c2ecf20Sopenharmony_ci strlcpy(serio->name, "AMS DELTA keyboard adapter", sizeof(serio->name)); 1638c2ecf20Sopenharmony_ci strlcpy(serio->phys, dev_name(&pdev->dev), sizeof(serio->phys)); 1648c2ecf20Sopenharmony_ci serio->dev.parent = &pdev->dev; 1658c2ecf20Sopenharmony_ci serio->port_data = priv; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci serio_register_port(serio); 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, priv); 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci dev_info(&serio->dev, "%s\n", serio->name); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_ci} 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_cistatic int ams_delta_serio_exit(struct platform_device *pdev) 1778c2ecf20Sopenharmony_ci{ 1788c2ecf20Sopenharmony_ci struct ams_delta_serio *priv = platform_get_drvdata(pdev); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci serio_unregister_port(priv->serio); 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci return 0; 1838c2ecf20Sopenharmony_ci} 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_cistatic struct platform_driver ams_delta_serio_driver = { 1868c2ecf20Sopenharmony_ci .probe = ams_delta_serio_init, 1878c2ecf20Sopenharmony_ci .remove = ams_delta_serio_exit, 1888c2ecf20Sopenharmony_ci .driver = { 1898c2ecf20Sopenharmony_ci .name = DRIVER_NAME 1908c2ecf20Sopenharmony_ci }, 1918c2ecf20Sopenharmony_ci}; 1928c2ecf20Sopenharmony_cimodule_platform_driver(ams_delta_serio_driver); 193