2011-11-03 00:39:38 +04:00
/*
* pps - gpio . c - - PPS client driver using GPIO
*
*
* Copyright ( C ) 2010 Ricardo Martins < rasm @ fe . up . pt >
* Copyright ( C ) 2011 James Nuss < jamesnuss @ nanometrics . ca >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# define PPS_GPIO_NAME "pps-gpio"
# define pr_fmt(fmt) PPS_GPIO_NAME ": " fmt
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/pps_kernel.h>
# include <linux/pps-gpio.h>
# include <linux/gpio.h>
# include <linux/list.h>
2013-07-04 02:09:12 +04:00
# include <linux/of_device.h>
# include <linux/of_gpio.h>
2011-11-03 00:39:38 +04:00
/* Info for each registered platform device */
struct pps_gpio_device_data {
int irq ; /* IRQ used as PPS source */
struct pps_device * pps ; /* PPS source device */
struct pps_source_info info ; /* PPS source information */
2013-07-04 02:09:12 +04:00
bool assert_falling_edge ;
bool capture_clear ;
unsigned int gpio_pin ;
2011-11-03 00:39:38 +04:00
} ;
/*
* Report the PPS event
*/
static irqreturn_t pps_gpio_irq_handler ( int irq , void * data )
{
const struct pps_gpio_device_data * info ;
struct pps_event_time ts ;
int rising_edge ;
/* Get the time stamp first */
pps_get_ts ( & ts ) ;
info = data ;
2013-07-04 02:09:12 +04:00
rising_edge = gpio_get_value ( info - > gpio_pin ) ;
if ( ( rising_edge & & ! info - > assert_falling_edge ) | |
( ! rising_edge & & info - > assert_falling_edge ) )
2011-11-03 00:39:38 +04:00
pps_event ( info - > pps , & ts , PPS_CAPTUREASSERT , NULL ) ;
2013-07-04 02:09:12 +04:00
else if ( info - > capture_clear & &
( ( rising_edge & & info - > assert_falling_edge ) | |
( ! rising_edge & & ! info - > assert_falling_edge ) ) )
2011-11-03 00:39:38 +04:00
pps_event ( info - > pps , & ts , PPS_CAPTURECLEAR , NULL ) ;
return IRQ_HANDLED ;
}
static unsigned long
2013-07-04 02:09:12 +04:00
get_irqf_trigger_flags ( const struct pps_gpio_device_data * data )
2011-11-03 00:39:38 +04:00
{
2013-07-04 02:09:12 +04:00
unsigned long flags = data - > assert_falling_edge ?
2011-11-03 00:39:38 +04:00
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING ;
2013-07-04 02:09:12 +04:00
if ( data - > capture_clear ) {
2011-11-03 00:39:38 +04:00
flags | = ( ( flags & IRQF_TRIGGER_RISING ) ?
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING ) ;
}
return flags ;
}
static int pps_gpio_probe ( struct platform_device * pdev )
{
struct pps_gpio_device_data * data ;
2013-07-04 02:09:12 +04:00
const char * gpio_label ;
2011-11-03 00:39:38 +04:00
int ret ;
int pps_default_params ;
const struct pps_gpio_platform_data * pdata = pdev - > dev . platform_data ;
2013-07-04 02:09:12 +04:00
struct device_node * np = pdev - > dev . of_node ;
/* allocate space for device info */
data = devm_kzalloc ( & pdev - > dev , sizeof ( struct pps_gpio_device_data ) ,
GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
2011-11-03 00:39:38 +04:00
2013-07-04 02:09:12 +04:00
if ( pdata ) {
data - > gpio_pin = pdata - > gpio_pin ;
gpio_label = pdata - > gpio_label ;
data - > assert_falling_edge = pdata - > assert_falling_edge ;
data - > capture_clear = pdata - > capture_clear ;
} else {
ret = of_get_gpio ( np , 0 ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to get GPIO from device tree \n " ) ;
return ret ;
}
data - > gpio_pin = ret ;
gpio_label = PPS_GPIO_NAME ;
if ( of_get_property ( np , " assert-falling-edge " , NULL ) )
data - > assert_falling_edge = true ;
}
2011-11-03 00:39:38 +04:00
/* GPIO setup */
2013-07-04 02:09:12 +04:00
ret = devm_gpio_request ( & pdev - > dev , data - > gpio_pin , gpio_label ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to request GPIO %u \n " ,
data - > gpio_pin ) ;
return ret ;
}
ret = gpio_direction_input ( data - > gpio_pin ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " failed to set pin direction \n " ) ;
2011-11-03 00:39:38 +04:00
return - EINVAL ;
2013-07-04 02:09:12 +04:00
}
2011-11-03 00:39:38 +04:00
/* IRQ setup */
2013-07-04 02:09:12 +04:00
ret = gpio_to_irq ( data - > gpio_pin ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " failed to map GPIO to IRQ: %d \n " , ret ) ;
2013-07-04 02:09:10 +04:00
return - EINVAL ;
2011-11-03 00:39:38 +04:00
}
2013-07-04 02:09:12 +04:00
data - > irq = ret ;
2011-11-03 00:39:38 +04:00
/* initialize PPS specific parts of the bookkeeping data structure. */
data - > info . mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC ;
2013-07-04 02:09:12 +04:00
if ( data - > capture_clear )
2011-11-03 00:39:38 +04:00
data - > info . mode | = PPS_CAPTURECLEAR | PPS_OFFSETCLEAR |
PPS_ECHOCLEAR ;
data - > info . owner = THIS_MODULE ;
snprintf ( data - > info . name , PPS_MAX_NAME_LEN - 1 , " %s.%d " ,
pdev - > name , pdev - > id ) ;
/* register PPS source */
pps_default_params = PPS_CAPTUREASSERT | PPS_OFFSETASSERT ;
2013-07-04 02:09:12 +04:00
if ( data - > capture_clear )
2011-11-03 00:39:38 +04:00
pps_default_params | = PPS_CAPTURECLEAR | PPS_OFFSETCLEAR ;
data - > pps = pps_register_source ( & data - > info , pps_default_params ) ;
if ( data - > pps = = NULL ) {
2013-07-04 02:09:12 +04:00
dev_err ( & pdev - > dev , " failed to register IRQ %d as PPS source \n " ,
data - > irq ) ;
2013-07-04 02:09:10 +04:00
return - EINVAL ;
2011-11-03 00:39:38 +04:00
}
/* register IRQ interrupt handler */
2013-07-04 02:09:12 +04:00
ret = devm_request_irq ( & pdev - > dev , data - > irq , pps_gpio_irq_handler ,
get_irqf_trigger_flags ( data ) , data - > info . name , data ) ;
2011-11-03 00:39:38 +04:00
if ( ret ) {
pps_unregister_source ( data - > pps ) ;
2013-07-04 02:09:12 +04:00
dev_err ( & pdev - > dev , " failed to acquire IRQ %d \n " , data - > irq ) ;
2013-07-04 02:09:10 +04:00
return - EINVAL ;
2011-11-03 00:39:38 +04:00
}
platform_set_drvdata ( pdev , data ) ;
2013-07-04 02:09:12 +04:00
dev_info ( data - > pps - > dev , " Registered IRQ %d as PPS source \n " ,
data - > irq ) ;
2011-11-03 00:39:38 +04:00
return 0 ;
}
static int pps_gpio_remove ( struct platform_device * pdev )
{
struct pps_gpio_device_data * data = platform_get_drvdata ( pdev ) ;
pps_unregister_source ( data - > pps ) ;
2013-07-04 02:09:12 +04:00
dev_info ( & pdev - > dev , " removed IRQ %d as PPS source \n " , data - > irq ) ;
2011-11-03 00:39:38 +04:00
return 0 ;
}
2013-07-04 02:09:12 +04:00
static const struct of_device_id pps_gpio_dt_ids [ ] = {
{ . compatible = " pps-gpio " , } ,
{ /* sentinel */ }
} ;
MODULE_DEVICE_TABLE ( of , pps_gpio_dt_ids ) ;
2011-11-03 00:39:38 +04:00
static struct platform_driver pps_gpio_driver = {
. probe = pps_gpio_probe ,
2012-12-22 03:14:44 +04:00
. remove = pps_gpio_remove ,
2011-11-03 00:39:38 +04:00
. driver = {
. name = PPS_GPIO_NAME ,
2013-11-13 03:11:33 +04:00
. of_match_table = pps_gpio_dt_ids ,
2011-11-03 00:39:38 +04:00
} ,
} ;
2013-07-04 02:09:10 +04:00
module_platform_driver ( pps_gpio_driver ) ;
2011-11-03 00:39:38 +04:00
MODULE_AUTHOR ( " Ricardo Martins <rasm@fe.up.pt> " ) ;
MODULE_AUTHOR ( " James Nuss <jamesnuss@nanometrics.ca> " ) ;
MODULE_DESCRIPTION ( " Use GPIO pin as PPS source " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( " 1.0.0 " ) ;