mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-09 01:28:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
41
drivers/pps/clients/Kconfig
Normal file
41
drivers/pps/clients/Kconfig
Normal file
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# PPS clients configuration
|
||||
#
|
||||
|
||||
if PPS
|
||||
|
||||
comment "PPS clients support"
|
||||
|
||||
config PPS_CLIENT_KTIMER
|
||||
tristate "Kernel timer client (Testing client, use for debug)"
|
||||
help
|
||||
If you say yes here you get support for a PPS debugging client
|
||||
which uses a kernel timer to generate the PPS signal.
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called pps-ktimer.
|
||||
|
||||
config PPS_CLIENT_LDISC
|
||||
tristate "PPS line discipline"
|
||||
depends on PPS && TTY
|
||||
help
|
||||
If you say yes here you get support for a PPS source connected
|
||||
with the CD (Carrier Detect) pin of your serial port.
|
||||
|
||||
config PPS_CLIENT_PARPORT
|
||||
tristate "Parallel port PPS client"
|
||||
depends on PPS && PARPORT
|
||||
help
|
||||
If you say yes here you get support for a PPS source connected
|
||||
with the interrupt pin of your parallel port.
|
||||
|
||||
config PPS_CLIENT_GPIO
|
||||
tristate "PPS client using GPIO"
|
||||
depends on PPS
|
||||
help
|
||||
If you say yes here you get support for a PPS source using
|
||||
GPIO. To be useful you must also register a platform device
|
||||
specifying the GPIO pin and other options, usually in your board
|
||||
setup.
|
||||
|
||||
endif
|
10
drivers/pps/clients/Makefile
Normal file
10
drivers/pps/clients/Makefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
#
|
||||
# Makefile for PPS clients.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_PPS_CLIENT_KTIMER) += pps-ktimer.o
|
||||
obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o
|
||||
obj-$(CONFIG_PPS_CLIENT_PARPORT) += pps_parport.o
|
||||
obj-$(CONFIG_PPS_CLIENT_GPIO) += pps-gpio.o
|
||||
|
||||
ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG
|
213
drivers/pps/clients/pps-gpio.c
Normal file
213
drivers/pps/clients/pps-gpio.c
Normal file
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* 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 02139, 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>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_gpio.h>
|
||||
|
||||
/* 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 */
|
||||
bool assert_falling_edge;
|
||||
bool capture_clear;
|
||||
unsigned int gpio_pin;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
rising_edge = gpio_get_value(info->gpio_pin);
|
||||
if ((rising_edge && !info->assert_falling_edge) ||
|
||||
(!rising_edge && info->assert_falling_edge))
|
||||
pps_event(info->pps, &ts, PPS_CAPTUREASSERT, NULL);
|
||||
else if (info->capture_clear &&
|
||||
((rising_edge && info->assert_falling_edge) ||
|
||||
(!rising_edge && !info->assert_falling_edge)))
|
||||
pps_event(info->pps, &ts, PPS_CAPTURECLEAR, NULL);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
get_irqf_trigger_flags(const struct pps_gpio_device_data *data)
|
||||
{
|
||||
unsigned long flags = data->assert_falling_edge ?
|
||||
IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
|
||||
|
||||
if (data->capture_clear) {
|
||||
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;
|
||||
const char *gpio_label;
|
||||
int ret;
|
||||
int pps_default_params;
|
||||
const struct pps_gpio_platform_data *pdata = pdev->dev.platform_data;
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* GPIO setup */
|
||||
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");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* IRQ setup */
|
||||
ret = gpio_to_irq(data->gpio_pin);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to map GPIO to IRQ: %d\n", ret);
|
||||
return -EINVAL;
|
||||
}
|
||||
data->irq = ret;
|
||||
|
||||
/* initialize PPS specific parts of the bookkeeping data structure. */
|
||||
data->info.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
|
||||
PPS_ECHOASSERT | PPS_CANWAIT | PPS_TSFMT_TSPEC;
|
||||
if (data->capture_clear)
|
||||
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;
|
||||
if (data->capture_clear)
|
||||
pps_default_params |= PPS_CAPTURECLEAR | PPS_OFFSETCLEAR;
|
||||
data->pps = pps_register_source(&data->info, pps_default_params);
|
||||
if (data->pps == NULL) {
|
||||
dev_err(&pdev->dev, "failed to register IRQ %d as PPS source\n",
|
||||
data->irq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* register IRQ interrupt handler */
|
||||
ret = devm_request_irq(&pdev->dev, data->irq, pps_gpio_irq_handler,
|
||||
get_irqf_trigger_flags(data), data->info.name, data);
|
||||
if (ret) {
|
||||
pps_unregister_source(data->pps);
|
||||
dev_err(&pdev->dev, "failed to acquire IRQ %d\n", data->irq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, data);
|
||||
dev_info(data->pps->dev, "Registered IRQ %d as PPS source\n",
|
||||
data->irq);
|
||||
|
||||
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);
|
||||
dev_info(&pdev->dev, "removed IRQ %d as PPS source\n", data->irq);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id pps_gpio_dt_ids[] = {
|
||||
{ .compatible = "pps-gpio", },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, pps_gpio_dt_ids);
|
||||
|
||||
static struct platform_driver pps_gpio_driver = {
|
||||
.probe = pps_gpio_probe,
|
||||
.remove = pps_gpio_remove,
|
||||
.driver = {
|
||||
.name = PPS_GPIO_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = pps_gpio_dt_ids,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(pps_gpio_driver);
|
||||
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");
|
101
drivers/pps/clients/pps-ktimer.c
Normal file
101
drivers/pps/clients/pps-ktimer.c
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* pps-ktimer.c -- kernel timer test client
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2005-2006 Rodolfo Giometti <giometti@linux.it>
|
||||
*
|
||||
* 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 02139, USA.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/pps_kernel.h>
|
||||
|
||||
/*
|
||||
* Global variables
|
||||
*/
|
||||
|
||||
static struct pps_device *pps;
|
||||
static struct timer_list ktimer;
|
||||
|
||||
/*
|
||||
* The kernel timer
|
||||
*/
|
||||
|
||||
static void pps_ktimer_event(unsigned long ptr)
|
||||
{
|
||||
struct pps_event_time ts;
|
||||
|
||||
/* First of all we get the time stamp... */
|
||||
pps_get_ts(&ts);
|
||||
|
||||
pps_event(pps, &ts, PPS_CAPTUREASSERT, NULL);
|
||||
|
||||
mod_timer(&ktimer, jiffies + HZ);
|
||||
}
|
||||
|
||||
/*
|
||||
* The PPS info struct
|
||||
*/
|
||||
|
||||
static struct pps_source_info pps_ktimer_info = {
|
||||
.name = "ktimer",
|
||||
.path = "",
|
||||
.mode = PPS_CAPTUREASSERT | PPS_OFFSETASSERT |
|
||||
PPS_ECHOASSERT |
|
||||
PPS_CANWAIT | PPS_TSFMT_TSPEC,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
/*
|
||||
* Module staff
|
||||
*/
|
||||
|
||||
static void __exit pps_ktimer_exit(void)
|
||||
{
|
||||
dev_info(pps->dev, "ktimer PPS source unregistered\n");
|
||||
|
||||
del_timer_sync(&ktimer);
|
||||
pps_unregister_source(pps);
|
||||
}
|
||||
|
||||
static int __init pps_ktimer_init(void)
|
||||
{
|
||||
pps = pps_register_source(&pps_ktimer_info,
|
||||
PPS_CAPTUREASSERT | PPS_OFFSETASSERT);
|
||||
if (pps == NULL) {
|
||||
pr_err("cannot register PPS source\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
setup_timer(&ktimer, pps_ktimer_event, 0);
|
||||
mod_timer(&ktimer, jiffies + HZ);
|
||||
|
||||
dev_info(pps->dev, "ktimer PPS source registered\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_init(pps_ktimer_init);
|
||||
module_exit(pps_ktimer_exit);
|
||||
|
||||
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
|
||||
MODULE_DESCRIPTION("dummy PPS source by using a kernel timer (just for debug)");
|
||||
MODULE_LICENSE("GPL");
|
163
drivers/pps/clients/pps-ldisc.c
Normal file
163
drivers/pps/clients/pps-ldisc.c
Normal file
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* pps-ldisc.c -- PPS line discipline
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it>
|
||||
*
|
||||
* 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 02139, USA.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/serial_core.h>
|
||||
#include <linux/tty.h>
|
||||
#include <linux/pps_kernel.h>
|
||||
#include <linux/bug.h>
|
||||
|
||||
#define PPS_TTY_MAGIC 0x0001
|
||||
|
||||
static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status)
|
||||
{
|
||||
struct pps_device *pps;
|
||||
struct pps_event_time ts;
|
||||
|
||||
pps_get_ts(&ts);
|
||||
|
||||
pps = pps_lookup_dev(tty);
|
||||
/*
|
||||
* This should never fail, but the ldisc locking is very
|
||||
* convoluted, so don't crash just in case.
|
||||
*/
|
||||
if (WARN_ON_ONCE(pps == NULL))
|
||||
return;
|
||||
|
||||
/* Now do the PPS event report */
|
||||
pps_event(pps, &ts, status ? PPS_CAPTUREASSERT :
|
||||
PPS_CAPTURECLEAR, NULL);
|
||||
|
||||
dev_dbg(pps->dev, "PPS %s at %lu\n",
|
||||
status ? "assert" : "clear", jiffies);
|
||||
}
|
||||
|
||||
static int (*alias_n_tty_open)(struct tty_struct *tty);
|
||||
|
||||
static int pps_tty_open(struct tty_struct *tty)
|
||||
{
|
||||
struct pps_source_info info;
|
||||
struct tty_driver *drv = tty->driver;
|
||||
int index = tty->index + drv->name_base;
|
||||
struct pps_device *pps;
|
||||
int ret;
|
||||
|
||||
info.owner = THIS_MODULE;
|
||||
info.dev = NULL;
|
||||
snprintf(info.name, PPS_MAX_NAME_LEN, "%s%d", drv->driver_name, index);
|
||||
snprintf(info.path, PPS_MAX_NAME_LEN, "/dev/%s%d", drv->name, index);
|
||||
info.mode = PPS_CAPTUREBOTH | \
|
||||
PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \
|
||||
PPS_CANWAIT | PPS_TSFMT_TSPEC;
|
||||
|
||||
pps = pps_register_source(&info, PPS_CAPTUREBOTH | \
|
||||
PPS_OFFSETASSERT | PPS_OFFSETCLEAR);
|
||||
if (pps == NULL) {
|
||||
pr_err("cannot register PPS source \"%s\"\n", info.path);
|
||||
return -ENOMEM;
|
||||
}
|
||||
pps->lookup_cookie = tty;
|
||||
|
||||
/* Now open the base class N_TTY ldisc */
|
||||
ret = alias_n_tty_open(tty);
|
||||
if (ret < 0) {
|
||||
pr_err("cannot open tty ldisc \"%s\"\n", info.path);
|
||||
goto err_unregister;
|
||||
}
|
||||
|
||||
dev_info(pps->dev, "source \"%s\" added\n", info.path);
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
pps_unregister_source(pps);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void (*alias_n_tty_close)(struct tty_struct *tty);
|
||||
|
||||
static void pps_tty_close(struct tty_struct *tty)
|
||||
{
|
||||
struct pps_device *pps = pps_lookup_dev(tty);
|
||||
|
||||
alias_n_tty_close(tty);
|
||||
|
||||
if (WARN_ON(!pps))
|
||||
return;
|
||||
|
||||
dev_info(pps->dev, "removed\n");
|
||||
pps_unregister_source(pps);
|
||||
}
|
||||
|
||||
static struct tty_ldisc_ops pps_ldisc_ops;
|
||||
|
||||
/*
|
||||
* Module stuff
|
||||
*/
|
||||
|
||||
static int __init pps_tty_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
/* Inherit the N_TTY's ops */
|
||||
n_tty_inherit_ops(&pps_ldisc_ops);
|
||||
|
||||
/* Save N_TTY's open()/close() methods */
|
||||
alias_n_tty_open = pps_ldisc_ops.open;
|
||||
alias_n_tty_close = pps_ldisc_ops.close;
|
||||
|
||||
/* Init PPS_TTY data */
|
||||
pps_ldisc_ops.owner = THIS_MODULE;
|
||||
pps_ldisc_ops.magic = PPS_TTY_MAGIC;
|
||||
pps_ldisc_ops.name = "pps_tty";
|
||||
pps_ldisc_ops.dcd_change = pps_tty_dcd_change;
|
||||
pps_ldisc_ops.open = pps_tty_open;
|
||||
pps_ldisc_ops.close = pps_tty_close;
|
||||
|
||||
err = tty_register_ldisc(N_PPS, &pps_ldisc_ops);
|
||||
if (err)
|
||||
pr_err("can't register PPS line discipline\n");
|
||||
else
|
||||
pr_info("PPS line discipline registered\n");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit pps_tty_cleanup(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = tty_unregister_ldisc(N_PPS);
|
||||
if (err)
|
||||
pr_err("can't unregister PPS line discipline\n");
|
||||
else
|
||||
pr_info("PPS line discipline removed\n");
|
||||
}
|
||||
|
||||
module_init(pps_tty_init);
|
||||
module_exit(pps_tty_cleanup);
|
||||
|
||||
MODULE_ALIAS_LDISC(N_PPS);
|
||||
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
|
||||
MODULE_DESCRIPTION("PPS TTY device driver");
|
||||
MODULE_LICENSE("GPL");
|
250
drivers/pps/clients/pps_parport.c
Normal file
250
drivers/pps/clients/pps_parport.c
Normal file
|
@ -0,0 +1,250 @@
|
|||
/*
|
||||
* pps_parport.c -- kernel parallel port PPS client
|
||||
*
|
||||
*
|
||||
* Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su>
|
||||
*
|
||||
* 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 02139, USA.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* implement echo over SEL pin
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/irqnr.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/parport.h>
|
||||
#include <linux/pps_kernel.h>
|
||||
|
||||
#define DRVDESC "parallel port PPS client"
|
||||
|
||||
/* module parameters */
|
||||
|
||||
#define CLEAR_WAIT_MAX 100
|
||||
#define CLEAR_WAIT_MAX_ERRORS 5
|
||||
|
||||
static unsigned int clear_wait = 100;
|
||||
MODULE_PARM_DESC(clear_wait,
|
||||
"Maximum number of port reads when polling for signal clear,"
|
||||
" zero turns clear edge capture off entirely");
|
||||
module_param(clear_wait, uint, 0);
|
||||
|
||||
|
||||
/* internal per port structure */
|
||||
struct pps_client_pp {
|
||||
struct pardevice *pardev; /* parport device */
|
||||
struct pps_device *pps; /* PPS device */
|
||||
unsigned int cw; /* port clear timeout */
|
||||
unsigned int cw_err; /* number of timeouts */
|
||||
};
|
||||
|
||||
static inline int signal_is_set(struct parport *port)
|
||||
{
|
||||
return (port->ops->read_status(port) & PARPORT_STATUS_ACK) != 0;
|
||||
}
|
||||
|
||||
/* parport interrupt handler */
|
||||
static void parport_irq(void *handle)
|
||||
{
|
||||
struct pps_event_time ts_assert, ts_clear;
|
||||
struct pps_client_pp *dev = handle;
|
||||
struct parport *port = dev->pardev->port;
|
||||
unsigned int i;
|
||||
unsigned long flags;
|
||||
|
||||
/* first of all we get the time stamp... */
|
||||
pps_get_ts(&ts_assert);
|
||||
|
||||
if (dev->cw == 0)
|
||||
/* clear edge capture disabled */
|
||||
goto out_assert;
|
||||
|
||||
/* try capture the clear edge */
|
||||
|
||||
/* We have to disable interrupts here. The idea is to prevent
|
||||
* other interrupts on the same processor to introduce random
|
||||
* lags while polling the port. Reading from IO port is known
|
||||
* to take approximately 1us while other interrupt handlers can
|
||||
* take much more potentially.
|
||||
*
|
||||
* Interrupts won't be disabled for a long time because the
|
||||
* number of polls is limited by clear_wait parameter which is
|
||||
* kept rather low. So it should never be an issue.
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
/* check the signal (no signal means the pulse is lost this time) */
|
||||
if (!signal_is_set(port)) {
|
||||
local_irq_restore(flags);
|
||||
dev_err(dev->pps->dev, "lost the signal\n");
|
||||
goto out_assert;
|
||||
}
|
||||
|
||||
/* poll the port until the signal is unset */
|
||||
for (i = dev->cw; i; i--)
|
||||
if (!signal_is_set(port)) {
|
||||
pps_get_ts(&ts_clear);
|
||||
local_irq_restore(flags);
|
||||
dev->cw_err = 0;
|
||||
goto out_both;
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
|
||||
/* timeout */
|
||||
dev->cw_err++;
|
||||
if (dev->cw_err >= CLEAR_WAIT_MAX_ERRORS) {
|
||||
dev_err(dev->pps->dev, "disabled clear edge capture after %d"
|
||||
" timeouts\n", dev->cw_err);
|
||||
dev->cw = 0;
|
||||
dev->cw_err = 0;
|
||||
}
|
||||
|
||||
out_assert:
|
||||
/* fire assert event */
|
||||
pps_event(dev->pps, &ts_assert,
|
||||
PPS_CAPTUREASSERT, NULL);
|
||||
return;
|
||||
|
||||
out_both:
|
||||
/* fire assert event */
|
||||
pps_event(dev->pps, &ts_assert,
|
||||
PPS_CAPTUREASSERT, NULL);
|
||||
/* fire clear event */
|
||||
pps_event(dev->pps, &ts_clear,
|
||||
PPS_CAPTURECLEAR, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
static void parport_attach(struct parport *port)
|
||||
{
|
||||
struct pps_client_pp *device;
|
||||
struct pps_source_info info = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.path = "",
|
||||
.mode = PPS_CAPTUREBOTH | \
|
||||
PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \
|
||||
PPS_ECHOASSERT | PPS_ECHOCLEAR | \
|
||||
PPS_CANWAIT | PPS_TSFMT_TSPEC,
|
||||
.owner = THIS_MODULE,
|
||||
.dev = NULL
|
||||
};
|
||||
|
||||
device = kzalloc(sizeof(struct pps_client_pp), GFP_KERNEL);
|
||||
if (!device) {
|
||||
pr_err("memory allocation failed, not attaching\n");
|
||||
return;
|
||||
}
|
||||
|
||||
device->pardev = parport_register_device(port, KBUILD_MODNAME,
|
||||
NULL, NULL, parport_irq, PARPORT_FLAG_EXCL, device);
|
||||
if (!device->pardev) {
|
||||
pr_err("couldn't register with %s\n", port->name);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (parport_claim_or_block(device->pardev) < 0) {
|
||||
pr_err("couldn't claim %s\n", port->name);
|
||||
goto err_unregister_dev;
|
||||
}
|
||||
|
||||
device->pps = pps_register_source(&info,
|
||||
PPS_CAPTUREBOTH | PPS_OFFSETASSERT | PPS_OFFSETCLEAR);
|
||||
if (device->pps == NULL) {
|
||||
pr_err("couldn't register PPS source\n");
|
||||
goto err_release_dev;
|
||||
}
|
||||
|
||||
device->cw = clear_wait;
|
||||
|
||||
port->ops->enable_irq(port);
|
||||
|
||||
pr_info("attached to %s\n", port->name);
|
||||
|
||||
return;
|
||||
|
||||
err_release_dev:
|
||||
parport_release(device->pardev);
|
||||
err_unregister_dev:
|
||||
parport_unregister_device(device->pardev);
|
||||
err_free:
|
||||
kfree(device);
|
||||
}
|
||||
|
||||
static void parport_detach(struct parport *port)
|
||||
{
|
||||
struct pardevice *pardev = port->cad;
|
||||
struct pps_client_pp *device;
|
||||
|
||||
/* FIXME: oooh, this is ugly! */
|
||||
if (strcmp(pardev->name, KBUILD_MODNAME))
|
||||
/* not our port */
|
||||
return;
|
||||
|
||||
device = pardev->private;
|
||||
|
||||
port->ops->disable_irq(port);
|
||||
pps_unregister_source(device->pps);
|
||||
parport_release(pardev);
|
||||
parport_unregister_device(pardev);
|
||||
kfree(device);
|
||||
}
|
||||
|
||||
static struct parport_driver pps_parport_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.attach = parport_attach,
|
||||
.detach = parport_detach,
|
||||
};
|
||||
|
||||
/* module staff */
|
||||
|
||||
static int __init pps_parport_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pr_info(DRVDESC "\n");
|
||||
|
||||
if (clear_wait > CLEAR_WAIT_MAX) {
|
||||
pr_err("clear_wait value should be not greater"
|
||||
" then %d\n", CLEAR_WAIT_MAX);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = parport_register_driver(&pps_parport_driver);
|
||||
if (ret) {
|
||||
pr_err("unable to register with parport\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit pps_parport_exit(void)
|
||||
{
|
||||
parport_unregister_driver(&pps_parport_driver);
|
||||
}
|
||||
|
||||
module_init(pps_parport_init);
|
||||
module_exit(pps_parport_exit);
|
||||
|
||||
MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
|
||||
MODULE_DESCRIPTION(DRVDESC);
|
||||
MODULE_LICENSE("GPL");
|
Loading…
Add table
Add a link
Reference in a new issue