116152045SJames Nuss /* 216152045SJames Nuss * pps-gpio.c -- PPS client driver using GPIO 316152045SJames Nuss * 416152045SJames Nuss * 516152045SJames Nuss * Copyright (C) 2010 Ricardo Martins <rasm@fe.up.pt> 616152045SJames Nuss * Copyright (C) 2011 James Nuss <jamesnuss@nanometrics.ca> 716152045SJames Nuss * 816152045SJames Nuss * This program is free software; you can redistribute it and/or modify 916152045SJames Nuss * it under the terms of the GNU General Public License as published by 1016152045SJames Nuss * the Free Software Foundation; either version 2 of the License, or 1116152045SJames Nuss * (at your option) any later version. 1216152045SJames Nuss * 1316152045SJames Nuss * This program is distributed in the hope that it will be useful, 1416152045SJames Nuss * but WITHOUT ANY WARRANTY; without even the implied warranty of 1516152045SJames Nuss * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1616152045SJames Nuss * GNU General Public License for more details. 1716152045SJames Nuss * 1816152045SJames Nuss * You should have received a copy of the GNU General Public License 1916152045SJames Nuss * along with this program; if not, write to the Free Software 2016152045SJames Nuss * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 2116152045SJames Nuss */ 2216152045SJames Nuss 2316152045SJames Nuss #define PPS_GPIO_NAME "pps-gpio" 2416152045SJames Nuss #define pr_fmt(fmt) PPS_GPIO_NAME ": " fmt 2516152045SJames Nuss 2616152045SJames Nuss #include <linux/init.h> 2716152045SJames Nuss #include <linux/kernel.h> 2816152045SJames Nuss #include <linux/interrupt.h> 2916152045SJames Nuss #include <linux/module.h> 3016152045SJames Nuss #include <linux/platform_device.h> 3116152045SJames Nuss #include <linux/slab.h> 3216152045SJames Nuss #include <linux/pps_kernel.h> 3316152045SJames Nuss #include <linux/pps-gpio.h> 344461d651STom Burkart #include <linux/gpio/consumer.h> 3516152045SJames Nuss #include <linux/list.h> 36c5dbcf8bSJan Luebbe #include <linux/of_device.h> 37c5dbcf8bSJan Luebbe #include <linux/of_gpio.h> 3816152045SJames Nuss 3916152045SJames Nuss /* Info for each registered platform device */ 4016152045SJames Nuss struct pps_gpio_device_data { 4116152045SJames Nuss int irq; /* IRQ used as PPS source */ 4216152045SJames Nuss struct pps_device *pps; /* PPS source device */ 4316152045SJames Nuss struct pps_source_info info; /* PPS source information */ 444461d651STom Burkart struct gpio_desc *gpio_pin; /* GPIO port descriptors */ 45c5dbcf8bSJan Luebbe bool assert_falling_edge; 46c5dbcf8bSJan Luebbe bool capture_clear; 4716152045SJames Nuss }; 4816152045SJames Nuss 4916152045SJames Nuss /* 5016152045SJames Nuss * Report the PPS event 5116152045SJames Nuss */ 5216152045SJames Nuss 5316152045SJames Nuss static irqreturn_t pps_gpio_irq_handler(int irq, void *data) 5416152045SJames Nuss { 5516152045SJames Nuss const struct pps_gpio_device_data *info; 5616152045SJames Nuss struct pps_event_time ts; 5716152045SJames Nuss int rising_edge; 5816152045SJames Nuss 5916152045SJames Nuss /* Get the time stamp first */ 6016152045SJames Nuss pps_get_ts(&ts); 6116152045SJames Nuss 6216152045SJames Nuss info = data; 6316152045SJames Nuss 644461d651STom Burkart rising_edge = gpiod_get_value(info->gpio_pin); 65c5dbcf8bSJan Luebbe if ((rising_edge && !info->assert_falling_edge) || 66c5dbcf8bSJan Luebbe (!rising_edge && info->assert_falling_edge)) 6716152045SJames Nuss pps_event(info->pps, &ts, PPS_CAPTUREASSERT, NULL); 68c5dbcf8bSJan Luebbe else if (info->capture_clear && 69c5dbcf8bSJan Luebbe ((rising_edge && info->assert_falling_edge) || 70c5dbcf8bSJan Luebbe (!rising_edge && !info->assert_falling_edge))) 7116152045SJames Nuss pps_event(info->pps, &ts, PPS_CAPTURECLEAR, NULL); 7216152045SJames Nuss 7316152045SJames Nuss return IRQ_HANDLED; 7416152045SJames Nuss } 7516152045SJames Nuss 764461d651STom Burkart static int pps_gpio_setup(struct platform_device *pdev) 774461d651STom Burkart { 784461d651STom Burkart struct pps_gpio_device_data *data = platform_get_drvdata(pdev); 794461d651STom Burkart struct device_node *np = pdev->dev.of_node; 804461d651STom Burkart 814461d651STom Burkart data->gpio_pin = devm_gpiod_get(&pdev->dev, 824461d651STom Burkart NULL, /* request "gpios" */ 834461d651STom Burkart GPIOD_IN); 844461d651STom Burkart if (IS_ERR(data->gpio_pin)) { 854461d651STom Burkart dev_err(&pdev->dev, 864461d651STom Burkart "failed to request PPS GPIO\n"); 874461d651STom Burkart return PTR_ERR(data->gpio_pin); 884461d651STom Burkart } 894461d651STom Burkart 904461d651STom Burkart if (of_property_read_bool(np, "assert-falling-edge")) 914461d651STom Burkart data->assert_falling_edge = true; 924461d651STom Burkart return 0; 934461d651STom Burkart } 944461d651STom Burkart 9516152045SJames Nuss static unsigned long 96c5dbcf8bSJan Luebbe get_irqf_trigger_flags(const struct pps_gpio_device_data *data) 9716152045SJames Nuss { 98c5dbcf8bSJan Luebbe unsigned long flags = data->assert_falling_edge ? 9916152045SJames Nuss IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; 10016152045SJames Nuss 101c5dbcf8bSJan Luebbe if (data->capture_clear) { 10216152045SJames Nuss flags |= ((flags & IRQF_TRIGGER_RISING) ? 10316152045SJames Nuss IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING); 10416152045SJames Nuss } 10516152045SJames Nuss 10616152045SJames Nuss return flags; 10716152045SJames Nuss } 10816152045SJames Nuss 10916152045SJames Nuss static int pps_gpio_probe(struct platform_device *pdev) 11016152045SJames Nuss { 11116152045SJames Nuss struct pps_gpio_device_data *data; 11216152045SJames Nuss int ret; 11316152045SJames Nuss int pps_default_params; 11416152045SJames Nuss const struct pps_gpio_platform_data *pdata = pdev->dev.platform_data; 11516152045SJames Nuss 11616152045SJames Nuss /* allocate space for device info */ 1174461d651STom Burkart data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); 118c5dbcf8bSJan Luebbe if (!data) 1192a651822SJan Luebbe return -ENOMEM; 1204461d651STom Burkart platform_set_drvdata(pdev, data); 12116152045SJames Nuss 1224461d651STom Burkart /* GPIO setup */ 123c5dbcf8bSJan Luebbe if (pdata) { 124c5dbcf8bSJan Luebbe data->gpio_pin = pdata->gpio_pin; 125c5dbcf8bSJan Luebbe 126c5dbcf8bSJan Luebbe data->assert_falling_edge = pdata->assert_falling_edge; 127c5dbcf8bSJan Luebbe data->capture_clear = pdata->capture_clear; 128c5dbcf8bSJan Luebbe } else { 1294461d651STom Burkart ret = pps_gpio_setup(pdev); 1304461d651STom Burkart if (ret) 131c5dbcf8bSJan Luebbe return -EINVAL; 132c5dbcf8bSJan Luebbe } 133c5dbcf8bSJan Luebbe 134c5dbcf8bSJan Luebbe /* IRQ setup */ 1354461d651STom Burkart ret = gpiod_to_irq(data->gpio_pin); 136c5dbcf8bSJan Luebbe if (ret < 0) { 137c5dbcf8bSJan Luebbe dev_err(&pdev->dev, "failed to map GPIO to IRQ: %d\n", ret); 138c5dbcf8bSJan Luebbe return -EINVAL; 139c5dbcf8bSJan Luebbe } 140c5dbcf8bSJan Luebbe data->irq = ret; 141c5dbcf8bSJan Luebbe 14216152045SJames Nuss /* initialize PPS specific parts of the bookkeeping data structure. */ 14316152045SJames Nuss data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT | 14416152045SJames Nuss PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC; 145c5dbcf8bSJan Luebbe if (data->capture_clear) 14616152045SJames Nuss data->info.mode |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR | 14716152045SJames Nuss PPS_ECHOCLEAR; 14816152045SJames Nuss data->info.owner = THIS_MODULE; 14916152045SJames Nuss snprintf(data->info.name, PPS_MAX_NAME_LEN - 1, "%s.%d", 15016152045SJames Nuss pdev->name, pdev->id); 15116152045SJames Nuss 15216152045SJames Nuss /* register PPS source */ 15316152045SJames Nuss pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT; 154c5dbcf8bSJan Luebbe if (data->capture_clear) 15516152045SJames Nuss pps_default_params |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR; 15616152045SJames Nuss data->pps = pps_register_source(&data->info, pps_default_params); 1573b1ad360SYueHaibing if (IS_ERR(data->pps)) { 158c5dbcf8bSJan Luebbe dev_err(&pdev->dev, "failed to register IRQ %d as PPS source\n", 159c5dbcf8bSJan Luebbe data->irq); 1603b1ad360SYueHaibing return PTR_ERR(data->pps); 16116152045SJames Nuss } 16216152045SJames Nuss 16316152045SJames Nuss /* register IRQ interrupt handler */ 164c5dbcf8bSJan Luebbe ret = devm_request_irq(&pdev->dev, data->irq, pps_gpio_irq_handler, 165c5dbcf8bSJan Luebbe get_irqf_trigger_flags(data), data->info.name, data); 16616152045SJames Nuss if (ret) { 16716152045SJames Nuss pps_unregister_source(data->pps); 168c5dbcf8bSJan Luebbe dev_err(&pdev->dev, "failed to acquire IRQ %d\n", data->irq); 1692a651822SJan Luebbe return -EINVAL; 17016152045SJames Nuss } 17116152045SJames Nuss 172c5dbcf8bSJan Luebbe dev_info(data->pps->dev, "Registered IRQ %d as PPS source\n", 173c5dbcf8bSJan Luebbe data->irq); 17416152045SJames Nuss 17516152045SJames Nuss return 0; 17616152045SJames Nuss } 17716152045SJames Nuss 17816152045SJames Nuss static int pps_gpio_remove(struct platform_device *pdev) 17916152045SJames Nuss { 18016152045SJames Nuss struct pps_gpio_device_data *data = platform_get_drvdata(pdev); 18116152045SJames Nuss 18216152045SJames Nuss pps_unregister_source(data->pps); 183c5dbcf8bSJan Luebbe dev_info(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq); 18416152045SJames Nuss return 0; 18516152045SJames Nuss } 18616152045SJames Nuss 187c5dbcf8bSJan Luebbe static const struct of_device_id pps_gpio_dt_ids[] = { 188c5dbcf8bSJan Luebbe { .compatible = "pps-gpio", }, 189c5dbcf8bSJan Luebbe { /* sentinel */ } 190c5dbcf8bSJan Luebbe }; 191c5dbcf8bSJan Luebbe MODULE_DEVICE_TABLE(of, pps_gpio_dt_ids); 192c5dbcf8bSJan Luebbe 19316152045SJames Nuss static struct platform_driver pps_gpio_driver = { 19416152045SJames Nuss .probe = pps_gpio_probe, 1950fe763c5SGreg Kroah-Hartman .remove = pps_gpio_remove, 19616152045SJames Nuss .driver = { 19716152045SJames Nuss .name = PPS_GPIO_NAME, 1981a10bd94SSachin Kamat .of_match_table = pps_gpio_dt_ids, 19916152045SJames Nuss }, 20016152045SJames Nuss }; 20116152045SJames Nuss 20205212be3SJan Luebbe module_platform_driver(pps_gpio_driver); 20316152045SJames Nuss MODULE_AUTHOR("Ricardo Martins <rasm@fe.up.pt>"); 20416152045SJames Nuss MODULE_AUTHOR("James Nuss <jamesnuss@nanometrics.ca>"); 20516152045SJames Nuss MODULE_DESCRIPTION("Use GPIO pin as PPS source"); 20616152045SJames Nuss MODULE_LICENSE("GPL"); 2074461d651STom Burkart MODULE_VERSION("1.1.0"); 208