18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * pps_parport.c -- kernel parallel port PPS client 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci/* 108c2ecf20Sopenharmony_ci * TODO: 118c2ecf20Sopenharmony_ci * implement echo over SEL pin 128c2ecf20Sopenharmony_ci */ 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/module.h> 188c2ecf20Sopenharmony_ci#include <linux/init.h> 198c2ecf20Sopenharmony_ci#include <linux/irqnr.h> 208c2ecf20Sopenharmony_ci#include <linux/time.h> 218c2ecf20Sopenharmony_ci#include <linux/slab.h> 228c2ecf20Sopenharmony_ci#include <linux/parport.h> 238c2ecf20Sopenharmony_ci#include <linux/pps_kernel.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#define DRVDESC "parallel port PPS client" 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* module parameters */ 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define CLEAR_WAIT_MAX 100 308c2ecf20Sopenharmony_ci#define CLEAR_WAIT_MAX_ERRORS 5 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic unsigned int clear_wait = 100; 338c2ecf20Sopenharmony_ciMODULE_PARM_DESC(clear_wait, 348c2ecf20Sopenharmony_ci "Maximum number of port reads when polling for signal clear," 358c2ecf20Sopenharmony_ci " zero turns clear edge capture off entirely"); 368c2ecf20Sopenharmony_cimodule_param(clear_wait, uint, 0); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_cistatic DEFINE_IDA(pps_client_index); 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci/* internal per port structure */ 418c2ecf20Sopenharmony_cistruct pps_client_pp { 428c2ecf20Sopenharmony_ci struct pardevice *pardev; /* parport device */ 438c2ecf20Sopenharmony_ci struct pps_device *pps; /* PPS device */ 448c2ecf20Sopenharmony_ci unsigned int cw; /* port clear timeout */ 458c2ecf20Sopenharmony_ci unsigned int cw_err; /* number of timeouts */ 468c2ecf20Sopenharmony_ci int index; /* device number */ 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic inline int signal_is_set(struct parport *port) 508c2ecf20Sopenharmony_ci{ 518c2ecf20Sopenharmony_ci return (port->ops->read_status(port) & PARPORT_STATUS_ACK) != 0; 528c2ecf20Sopenharmony_ci} 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* parport interrupt handler */ 558c2ecf20Sopenharmony_cistatic void parport_irq(void *handle) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci struct pps_event_time ts_assert, ts_clear; 588c2ecf20Sopenharmony_ci struct pps_client_pp *dev = handle; 598c2ecf20Sopenharmony_ci struct parport *port = dev->pardev->port; 608c2ecf20Sopenharmony_ci unsigned int i; 618c2ecf20Sopenharmony_ci unsigned long flags; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci /* first of all we get the time stamp... */ 648c2ecf20Sopenharmony_ci pps_get_ts(&ts_assert); 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci if (dev->cw == 0) 678c2ecf20Sopenharmony_ci /* clear edge capture disabled */ 688c2ecf20Sopenharmony_ci goto out_assert; 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci /* try capture the clear edge */ 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci /* We have to disable interrupts here. The idea is to prevent 738c2ecf20Sopenharmony_ci * other interrupts on the same processor to introduce random 748c2ecf20Sopenharmony_ci * lags while polling the port. Reading from IO port is known 758c2ecf20Sopenharmony_ci * to take approximately 1us while other interrupt handlers can 768c2ecf20Sopenharmony_ci * take much more potentially. 778c2ecf20Sopenharmony_ci * 788c2ecf20Sopenharmony_ci * Interrupts won't be disabled for a long time because the 798c2ecf20Sopenharmony_ci * number of polls is limited by clear_wait parameter which is 808c2ecf20Sopenharmony_ci * kept rather low. So it should never be an issue. 818c2ecf20Sopenharmony_ci */ 828c2ecf20Sopenharmony_ci local_irq_save(flags); 838c2ecf20Sopenharmony_ci /* check the signal (no signal means the pulse is lost this time) */ 848c2ecf20Sopenharmony_ci if (!signal_is_set(port)) { 858c2ecf20Sopenharmony_ci local_irq_restore(flags); 868c2ecf20Sopenharmony_ci dev_err(dev->pps->dev, "lost the signal\n"); 878c2ecf20Sopenharmony_ci goto out_assert; 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci /* poll the port until the signal is unset */ 918c2ecf20Sopenharmony_ci for (i = dev->cw; i; i--) 928c2ecf20Sopenharmony_ci if (!signal_is_set(port)) { 938c2ecf20Sopenharmony_ci pps_get_ts(&ts_clear); 948c2ecf20Sopenharmony_ci local_irq_restore(flags); 958c2ecf20Sopenharmony_ci dev->cw_err = 0; 968c2ecf20Sopenharmony_ci goto out_both; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci local_irq_restore(flags); 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci /* timeout */ 1018c2ecf20Sopenharmony_ci dev->cw_err++; 1028c2ecf20Sopenharmony_ci if (dev->cw_err >= CLEAR_WAIT_MAX_ERRORS) { 1038c2ecf20Sopenharmony_ci dev_err(dev->pps->dev, "disabled clear edge capture after %d" 1048c2ecf20Sopenharmony_ci " timeouts\n", dev->cw_err); 1058c2ecf20Sopenharmony_ci dev->cw = 0; 1068c2ecf20Sopenharmony_ci dev->cw_err = 0; 1078c2ecf20Sopenharmony_ci } 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ciout_assert: 1108c2ecf20Sopenharmony_ci /* fire assert event */ 1118c2ecf20Sopenharmony_ci pps_event(dev->pps, &ts_assert, 1128c2ecf20Sopenharmony_ci PPS_CAPTUREASSERT, NULL); 1138c2ecf20Sopenharmony_ci return; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ciout_both: 1168c2ecf20Sopenharmony_ci /* fire assert event */ 1178c2ecf20Sopenharmony_ci pps_event(dev->pps, &ts_assert, 1188c2ecf20Sopenharmony_ci PPS_CAPTUREASSERT, NULL); 1198c2ecf20Sopenharmony_ci /* fire clear event */ 1208c2ecf20Sopenharmony_ci pps_event(dev->pps, &ts_clear, 1218c2ecf20Sopenharmony_ci PPS_CAPTURECLEAR, NULL); 1228c2ecf20Sopenharmony_ci return; 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic void parport_attach(struct parport *port) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct pardev_cb pps_client_cb; 1288c2ecf20Sopenharmony_ci int index; 1298c2ecf20Sopenharmony_ci struct pps_client_pp *device; 1308c2ecf20Sopenharmony_ci struct pps_source_info info = { 1318c2ecf20Sopenharmony_ci .name = KBUILD_MODNAME, 1328c2ecf20Sopenharmony_ci .path = "", 1338c2ecf20Sopenharmony_ci .mode = PPS_CAPTUREBOTH | \ 1348c2ecf20Sopenharmony_ci PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \ 1358c2ecf20Sopenharmony_ci PPS_ECHOASSERT | PPS_ECHOCLEAR | \ 1368c2ecf20Sopenharmony_ci PPS_CANWAIT | PPS_TSFMT_TSPEC, 1378c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1388c2ecf20Sopenharmony_ci .dev = NULL 1398c2ecf20Sopenharmony_ci }; 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci device = kzalloc(sizeof(struct pps_client_pp), GFP_KERNEL); 1428c2ecf20Sopenharmony_ci if (!device) { 1438c2ecf20Sopenharmony_ci pr_err("memory allocation failed, not attaching\n"); 1448c2ecf20Sopenharmony_ci return; 1458c2ecf20Sopenharmony_ci } 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci index = ida_simple_get(&pps_client_index, 0, 0, GFP_KERNEL); 1488c2ecf20Sopenharmony_ci memset(&pps_client_cb, 0, sizeof(pps_client_cb)); 1498c2ecf20Sopenharmony_ci pps_client_cb.private = device; 1508c2ecf20Sopenharmony_ci pps_client_cb.irq_func = parport_irq; 1518c2ecf20Sopenharmony_ci pps_client_cb.flags = PARPORT_FLAG_EXCL; 1528c2ecf20Sopenharmony_ci device->pardev = parport_register_dev_model(port, 1538c2ecf20Sopenharmony_ci KBUILD_MODNAME, 1548c2ecf20Sopenharmony_ci &pps_client_cb, 1558c2ecf20Sopenharmony_ci index); 1568c2ecf20Sopenharmony_ci if (!device->pardev) { 1578c2ecf20Sopenharmony_ci pr_err("couldn't register with %s\n", port->name); 1588c2ecf20Sopenharmony_ci goto err_free; 1598c2ecf20Sopenharmony_ci } 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci if (parport_claim_or_block(device->pardev) < 0) { 1628c2ecf20Sopenharmony_ci pr_err("couldn't claim %s\n", port->name); 1638c2ecf20Sopenharmony_ci goto err_unregister_dev; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci device->pps = pps_register_source(&info, 1678c2ecf20Sopenharmony_ci PPS_CAPTUREBOTH | PPS_OFFSETASSERT | PPS_OFFSETCLEAR); 1688c2ecf20Sopenharmony_ci if (IS_ERR(device->pps)) { 1698c2ecf20Sopenharmony_ci pr_err("couldn't register PPS source\n"); 1708c2ecf20Sopenharmony_ci goto err_release_dev; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci device->cw = clear_wait; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci port->ops->enable_irq(port); 1768c2ecf20Sopenharmony_ci device->index = index; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci pr_info("attached to %s\n", port->name); 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci return; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cierr_release_dev: 1838c2ecf20Sopenharmony_ci parport_release(device->pardev); 1848c2ecf20Sopenharmony_cierr_unregister_dev: 1858c2ecf20Sopenharmony_ci parport_unregister_device(device->pardev); 1868c2ecf20Sopenharmony_cierr_free: 1878c2ecf20Sopenharmony_ci ida_simple_remove(&pps_client_index, index); 1888c2ecf20Sopenharmony_ci kfree(device); 1898c2ecf20Sopenharmony_ci} 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_cistatic void parport_detach(struct parport *port) 1928c2ecf20Sopenharmony_ci{ 1938c2ecf20Sopenharmony_ci struct pardevice *pardev = port->cad; 1948c2ecf20Sopenharmony_ci struct pps_client_pp *device; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* FIXME: oooh, this is ugly! */ 1978c2ecf20Sopenharmony_ci if (!pardev || strcmp(pardev->name, KBUILD_MODNAME)) 1988c2ecf20Sopenharmony_ci /* not our port */ 1998c2ecf20Sopenharmony_ci return; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci device = pardev->private; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci port->ops->disable_irq(port); 2048c2ecf20Sopenharmony_ci pps_unregister_source(device->pps); 2058c2ecf20Sopenharmony_ci parport_release(pardev); 2068c2ecf20Sopenharmony_ci parport_unregister_device(pardev); 2078c2ecf20Sopenharmony_ci ida_simple_remove(&pps_client_index, device->index); 2088c2ecf20Sopenharmony_ci kfree(device); 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic struct parport_driver pps_parport_driver = { 2128c2ecf20Sopenharmony_ci .name = KBUILD_MODNAME, 2138c2ecf20Sopenharmony_ci .match_port = parport_attach, 2148c2ecf20Sopenharmony_ci .detach = parport_detach, 2158c2ecf20Sopenharmony_ci .devmodel = true, 2168c2ecf20Sopenharmony_ci}; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci/* module staff */ 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_cistatic int __init pps_parport_init(void) 2218c2ecf20Sopenharmony_ci{ 2228c2ecf20Sopenharmony_ci int ret; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci pr_info(DRVDESC "\n"); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci if (clear_wait > CLEAR_WAIT_MAX) { 2278c2ecf20Sopenharmony_ci pr_err("clear_wait value should be not greater" 2288c2ecf20Sopenharmony_ci " then %d\n", CLEAR_WAIT_MAX); 2298c2ecf20Sopenharmony_ci return -EINVAL; 2308c2ecf20Sopenharmony_ci } 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci ret = parport_register_driver(&pps_parport_driver); 2338c2ecf20Sopenharmony_ci if (ret) { 2348c2ecf20Sopenharmony_ci pr_err("unable to register with parport\n"); 2358c2ecf20Sopenharmony_ci return ret; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci return 0; 2398c2ecf20Sopenharmony_ci} 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_cistatic void __exit pps_parport_exit(void) 2428c2ecf20Sopenharmony_ci{ 2438c2ecf20Sopenharmony_ci parport_unregister_driver(&pps_parport_driver); 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cimodule_init(pps_parport_init); 2478c2ecf20Sopenharmony_cimodule_exit(pps_parport_exit); 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ciMODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>"); 2508c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRVDESC); 2518c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 252