Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

93
drivers/ptp/Kconfig Normal file
View file

@ -0,0 +1,93 @@
#
# PTP clock support configuration
#
menu "PTP clock support"
config PTP_1588_CLOCK
tristate "PTP clock support"
depends on NET
select PPS
select NET_PTP_CLASSIFY
help
The IEEE 1588 standard defines a method to precisely
synchronize distributed clocks over Ethernet networks. The
standard defines a Precision Time Protocol (PTP), which can
be used to achieve synchronization within a few dozen
microseconds. In addition, with the help of special hardware
time stamping units, it can be possible to achieve
synchronization to within a few hundred nanoseconds.
This driver adds support for PTP clocks as character
devices. If you want to use a PTP clock, then you should
also enable at least one clock driver as well.
To compile this driver as a module, choose M here: the module
will be called ptp.
config PTP_1588_CLOCK_GIANFAR
tristate "Freescale eTSEC as PTP clock"
depends on GIANFAR
select PTP_1588_CLOCK
default y
help
This driver adds support for using the eTSEC as a PTP
clock. This clock is only useful if your PTP programs are
getting hardware time stamps on the PTP Ethernet packets
using the SO_TIMESTAMPING API.
To compile this driver as a module, choose M here: the module
will be called gianfar_ptp.
config PTP_1588_CLOCK_IXP46X
tristate "Intel IXP46x as PTP clock"
depends on IXP4XX_ETH
select PTP_1588_CLOCK
default y
help
This driver adds support for using the IXP46X as a PTP
clock. This clock is only useful if your PTP programs are
getting hardware time stamps on the PTP Ethernet packets
using the SO_TIMESTAMPING API.
To compile this driver as a module, choose M here: the module
will be called ptp_ixp46x.
comment "Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks."
depends on PHYLIB=n || NETWORK_PHY_TIMESTAMPING=n
config DP83640_PHY
tristate "Driver for the National Semiconductor DP83640 PHYTER"
depends on NETWORK_PHY_TIMESTAMPING
depends on PHYLIB
select PTP_1588_CLOCK
---help---
Supports the DP83640 PHYTER with IEEE 1588 features.
This driver adds support for using the DP83640 as a PTP
clock. This clock is only useful if your PTP programs are
getting hardware time stamps on the PTP Ethernet packets
using the SO_TIMESTAMPING API.
In order for this to work, your MAC driver must also
implement the skb_tx_timestamp() function.
config PTP_1588_CLOCK_PCH
tristate "Intel PCH EG20T as PTP clock"
depends on X86_32 || COMPILE_TEST
depends on HAS_IOMEM && NET
select PTP_1588_CLOCK
help
This driver adds support for using the PCH EG20T as a PTP
clock. The hardware supports time stamping of PTP packets
when using the end-to-end delay (E2E) mechansim. The peer
delay mechansim (P2P) is not supported.
This clock is only useful if your PTP programs are getting
hardware time stamps on the PTP Ethernet packets using the
SO_TIMESTAMPING API.
To compile this driver as a module, choose M here: the module
will be called ptp_pch.
endmenu

8
drivers/ptp/Makefile Normal file
View file

@ -0,0 +1,8 @@
#
# Makefile for PTP 1588 clock support.
#
ptp-y := ptp_clock.o ptp_chardev.o ptp_sysfs.o
obj-$(CONFIG_PTP_1588_CLOCK) += ptp.o
obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o
obj-$(CONFIG_PTP_1588_CLOCK_PCH) += ptp_pch.o

332
drivers/ptp/ptp_chardev.c Normal file
View file

@ -0,0 +1,332 @@
/*
* PTP 1588 clock support - character device implementation.
*
* Copyright (C) 2010 OMICRON electronics GmbH
*
* 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.
*/
#include <linux/module.h>
#include <linux/posix-clock.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include "ptp_private.h"
static int ptp_disable_pinfunc(struct ptp_clock_info *ops,
enum ptp_pin_function func, unsigned int chan)
{
struct ptp_clock_request rq;
int err = 0;
memset(&rq, 0, sizeof(rq));
switch (func) {
case PTP_PF_NONE:
break;
case PTP_PF_EXTTS:
rq.type = PTP_CLK_REQ_EXTTS;
rq.extts.index = chan;
err = ops->enable(ops, &rq, 0);
break;
case PTP_PF_PEROUT:
rq.type = PTP_CLK_REQ_PEROUT;
rq.perout.index = chan;
err = ops->enable(ops, &rq, 0);
break;
case PTP_PF_PHYSYNC:
break;
default:
return -EINVAL;
}
return err;
}
int ptp_set_pinfunc(struct ptp_clock *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan)
{
struct ptp_clock_info *info = ptp->info;
struct ptp_pin_desc *pin1 = NULL, *pin2 = &info->pin_config[pin];
unsigned int i;
/* Check to see if any other pin previously had this function. */
for (i = 0; i < info->n_pins; i++) {
if (info->pin_config[i].func == func &&
info->pin_config[i].chan == chan) {
pin1 = &info->pin_config[i];
break;
}
}
if (pin1 && i == pin)
return 0;
/* Check the desired function and channel. */
switch (func) {
case PTP_PF_NONE:
break;
case PTP_PF_EXTTS:
if (chan >= info->n_ext_ts)
return -EINVAL;
break;
case PTP_PF_PEROUT:
if (chan >= info->n_per_out)
return -EINVAL;
break;
case PTP_PF_PHYSYNC:
if (chan != 0)
return -EINVAL;
default:
return -EINVAL;
}
if (info->verify(info, pin, func, chan)) {
pr_err("driver cannot use function %u on pin %u\n", func, chan);
return -EOPNOTSUPP;
}
/* Disable whatever function was previously assigned. */
if (pin1) {
ptp_disable_pinfunc(info, func, chan);
pin1->func = PTP_PF_NONE;
pin1->chan = 0;
}
ptp_disable_pinfunc(info, pin2->func, pin2->chan);
pin2->func = func;
pin2->chan = chan;
return 0;
}
int ptp_open(struct posix_clock *pc, fmode_t fmode)
{
return 0;
}
long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
{
struct ptp_clock_caps caps;
struct ptp_clock_request req;
struct ptp_sys_offset *sysoff = NULL;
struct ptp_pin_desc pd;
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
struct ptp_clock_info *ops = ptp->info;
struct ptp_clock_time *pct;
struct timespec ts;
int enable, err = 0;
unsigned int i, pin_index;
switch (cmd) {
case PTP_CLOCK_GETCAPS:
memset(&caps, 0, sizeof(caps));
caps.max_adj = ptp->info->max_adj;
caps.n_alarm = ptp->info->n_alarm;
caps.n_ext_ts = ptp->info->n_ext_ts;
caps.n_per_out = ptp->info->n_per_out;
caps.pps = ptp->info->pps;
caps.n_pins = ptp->info->n_pins;
if (copy_to_user((void __user *)arg, &caps, sizeof(caps)))
err = -EFAULT;
break;
case PTP_EXTTS_REQUEST:
if (copy_from_user(&req.extts, (void __user *)arg,
sizeof(req.extts))) {
err = -EFAULT;
break;
}
if (req.extts.index >= ops->n_ext_ts) {
err = -EINVAL;
break;
}
req.type = PTP_CLK_REQ_EXTTS;
enable = req.extts.flags & PTP_ENABLE_FEATURE ? 1 : 0;
err = ops->enable(ops, &req, enable);
break;
case PTP_PEROUT_REQUEST:
if (copy_from_user(&req.perout, (void __user *)arg,
sizeof(req.perout))) {
err = -EFAULT;
break;
}
if (req.perout.index >= ops->n_per_out) {
err = -EINVAL;
break;
}
req.type = PTP_CLK_REQ_PEROUT;
enable = req.perout.period.sec || req.perout.period.nsec;
err = ops->enable(ops, &req, enable);
break;
case PTP_ENABLE_PPS:
if (!capable(CAP_SYS_TIME))
return -EPERM;
req.type = PTP_CLK_REQ_PPS;
enable = arg ? 1 : 0;
err = ops->enable(ops, &req, enable);
break;
case PTP_SYS_OFFSET:
sysoff = kmalloc(sizeof(*sysoff), GFP_KERNEL);
if (!sysoff) {
err = -ENOMEM;
break;
}
if (copy_from_user(sysoff, (void __user *)arg,
sizeof(*sysoff))) {
err = -EFAULT;
break;
}
if (sysoff->n_samples > PTP_MAX_SAMPLES) {
err = -EINVAL;
break;
}
pct = &sysoff->ts[0];
for (i = 0; i < sysoff->n_samples; i++) {
getnstimeofday(&ts);
pct->sec = ts.tv_sec;
pct->nsec = ts.tv_nsec;
pct++;
ptp->info->gettime(ptp->info, &ts);
pct->sec = ts.tv_sec;
pct->nsec = ts.tv_nsec;
pct++;
}
getnstimeofday(&ts);
pct->sec = ts.tv_sec;
pct->nsec = ts.tv_nsec;
if (copy_to_user((void __user *)arg, sysoff, sizeof(*sysoff)))
err = -EFAULT;
break;
case PTP_PIN_GETFUNC:
if (copy_from_user(&pd, (void __user *)arg, sizeof(pd))) {
err = -EFAULT;
break;
}
pin_index = pd.index;
if (pin_index >= ops->n_pins) {
err = -EINVAL;
break;
}
if (mutex_lock_interruptible(&ptp->pincfg_mux))
return -ERESTARTSYS;
pd = ops->pin_config[pin_index];
mutex_unlock(&ptp->pincfg_mux);
if (!err && copy_to_user((void __user *)arg, &pd, sizeof(pd)))
err = -EFAULT;
break;
case PTP_PIN_SETFUNC:
if (copy_from_user(&pd, (void __user *)arg, sizeof(pd))) {
err = -EFAULT;
break;
}
pin_index = pd.index;
if (pin_index >= ops->n_pins) {
err = -EINVAL;
break;
}
if (mutex_lock_interruptible(&ptp->pincfg_mux))
return -ERESTARTSYS;
err = ptp_set_pinfunc(ptp, pin_index, pd.func, pd.chan);
mutex_unlock(&ptp->pincfg_mux);
break;
default:
err = -ENOTTY;
break;
}
kfree(sysoff);
return err;
}
unsigned int ptp_poll(struct posix_clock *pc, struct file *fp, poll_table *wait)
{
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
poll_wait(fp, &ptp->tsev_wq, wait);
return queue_cnt(&ptp->tsevq) ? POLLIN : 0;
}
#define EXTTS_BUFSIZE (PTP_BUF_TIMESTAMPS * sizeof(struct ptp_extts_event))
ssize_t ptp_read(struct posix_clock *pc,
uint rdflags, char __user *buf, size_t cnt)
{
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
struct timestamp_event_queue *queue = &ptp->tsevq;
struct ptp_extts_event *event;
unsigned long flags;
size_t qcnt, i;
int result;
if (cnt % sizeof(struct ptp_extts_event) != 0)
return -EINVAL;
if (cnt > EXTTS_BUFSIZE)
cnt = EXTTS_BUFSIZE;
cnt = cnt / sizeof(struct ptp_extts_event);
if (mutex_lock_interruptible(&ptp->tsevq_mux))
return -ERESTARTSYS;
if (wait_event_interruptible(ptp->tsev_wq,
ptp->defunct || queue_cnt(queue))) {
mutex_unlock(&ptp->tsevq_mux);
return -ERESTARTSYS;
}
if (ptp->defunct) {
mutex_unlock(&ptp->tsevq_mux);
return -ENODEV;
}
event = kmalloc(EXTTS_BUFSIZE, GFP_KERNEL);
if (!event) {
mutex_unlock(&ptp->tsevq_mux);
return -ENOMEM;
}
spin_lock_irqsave(&queue->lock, flags);
qcnt = queue_cnt(queue);
if (cnt > qcnt)
cnt = qcnt;
for (i = 0; i < cnt; i++) {
event[i] = queue->buf[queue->head];
queue->head = (queue->head + 1) % PTP_MAX_TIMESTAMPS;
}
spin_unlock_irqrestore(&queue->lock, flags);
cnt = cnt * sizeof(struct ptp_extts_event);
mutex_unlock(&ptp->tsevq_mux);
result = cnt;
if (copy_to_user(buf, event, cnt))
result = -EFAULT;
kfree(event);
return result;
}

373
drivers/ptp/ptp_clock.c Normal file
View file

@ -0,0 +1,373 @@
/*
* PTP 1588 clock support
*
* Copyright (C) 2010 OMICRON electronics GmbH
*
* 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.
*/
#include <linux/idr.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/posix-clock.h>
#include <linux/pps_kernel.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include "ptp_private.h"
#define PTP_MAX_ALARMS 4
#define PTP_PPS_DEFAULTS (PPS_CAPTUREASSERT | PPS_OFFSETASSERT)
#define PTP_PPS_EVENT PPS_CAPTUREASSERT
#define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)
/* private globals */
static dev_t ptp_devt;
static struct class *ptp_class;
static DEFINE_IDA(ptp_clocks_map);
/* time stamp event queue operations */
static inline int queue_free(struct timestamp_event_queue *q)
{
return PTP_MAX_TIMESTAMPS - queue_cnt(q) - 1;
}
static void enqueue_external_timestamp(struct timestamp_event_queue *queue,
struct ptp_clock_event *src)
{
struct ptp_extts_event *dst;
unsigned long flags;
s64 seconds;
u32 remainder;
seconds = div_u64_rem(src->timestamp, 1000000000, &remainder);
spin_lock_irqsave(&queue->lock, flags);
dst = &queue->buf[queue->tail];
dst->index = src->index;
dst->t.sec = seconds;
dst->t.nsec = remainder;
if (!queue_free(queue))
queue->head = (queue->head + 1) % PTP_MAX_TIMESTAMPS;
queue->tail = (queue->tail + 1) % PTP_MAX_TIMESTAMPS;
spin_unlock_irqrestore(&queue->lock, flags);
}
static s32 scaled_ppm_to_ppb(long ppm)
{
/*
* The 'freq' field in the 'struct timex' is in parts per
* million, but with a 16 bit binary fractional field.
*
* We want to calculate
*
* ppb = scaled_ppm * 1000 / 2^16
*
* which simplifies to
*
* ppb = scaled_ppm * 125 / 2^13
*/
s64 ppb = 1 + ppm;
ppb *= 125;
ppb >>= 13;
return (s32) ppb;
}
/* posix clock implementation */
static int ptp_clock_getres(struct posix_clock *pc, struct timespec *tp)
{
tp->tv_sec = 0;
tp->tv_nsec = 1;
return 0;
}
static int ptp_clock_settime(struct posix_clock *pc, const struct timespec *tp)
{
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
return ptp->info->settime(ptp->info, tp);
}
static int ptp_clock_gettime(struct posix_clock *pc, struct timespec *tp)
{
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
return ptp->info->gettime(ptp->info, tp);
}
static int ptp_clock_adjtime(struct posix_clock *pc, struct timex *tx)
{
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
struct ptp_clock_info *ops;
int err = -EOPNOTSUPP;
ops = ptp->info;
if (tx->modes & ADJ_SETOFFSET) {
struct timespec ts;
ktime_t kt;
s64 delta;
ts.tv_sec = tx->time.tv_sec;
ts.tv_nsec = tx->time.tv_usec;
if (!(tx->modes & ADJ_NANO))
ts.tv_nsec *= 1000;
if ((unsigned long) ts.tv_nsec >= NSEC_PER_SEC)
return -EINVAL;
kt = timespec_to_ktime(ts);
delta = ktime_to_ns(kt);
err = ops->adjtime(ops, delta);
} else if (tx->modes & ADJ_FREQUENCY) {
s32 ppb = scaled_ppm_to_ppb(tx->freq);
if (ppb > ops->max_adj || ppb < -ops->max_adj)
return -ERANGE;
err = ops->adjfreq(ops, ppb);
ptp->dialed_frequency = tx->freq;
} else if (tx->modes == 0) {
tx->freq = ptp->dialed_frequency;
err = 0;
}
return err;
}
static struct posix_clock_operations ptp_clock_ops = {
.owner = THIS_MODULE,
.clock_adjtime = ptp_clock_adjtime,
.clock_gettime = ptp_clock_gettime,
.clock_getres = ptp_clock_getres,
.clock_settime = ptp_clock_settime,
.ioctl = ptp_ioctl,
.open = ptp_open,
.poll = ptp_poll,
.read = ptp_read,
};
static void delete_ptp_clock(struct posix_clock *pc)
{
struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
mutex_destroy(&ptp->tsevq_mux);
mutex_destroy(&ptp->pincfg_mux);
ida_simple_remove(&ptp_clocks_map, ptp->index);
kfree(ptp);
}
/* public interface */
struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
struct device *parent)
{
struct ptp_clock *ptp;
int err = 0, index, major = MAJOR(ptp_devt);
if (info->n_alarm > PTP_MAX_ALARMS)
return ERR_PTR(-EINVAL);
/* Initialize a clock structure. */
err = -ENOMEM;
ptp = kzalloc(sizeof(struct ptp_clock), GFP_KERNEL);
if (ptp == NULL)
goto no_memory;
index = ida_simple_get(&ptp_clocks_map, 0, MINORMASK + 1, GFP_KERNEL);
if (index < 0) {
err = index;
goto no_slot;
}
ptp->clock.ops = ptp_clock_ops;
ptp->clock.release = delete_ptp_clock;
ptp->info = info;
ptp->devid = MKDEV(major, index);
ptp->index = index;
spin_lock_init(&ptp->tsevq.lock);
mutex_init(&ptp->tsevq_mux);
mutex_init(&ptp->pincfg_mux);
init_waitqueue_head(&ptp->tsev_wq);
/* Create a new device in our class. */
ptp->dev = device_create(ptp_class, parent, ptp->devid, ptp,
"ptp%d", ptp->index);
if (IS_ERR(ptp->dev))
goto no_device;
dev_set_drvdata(ptp->dev, ptp);
err = ptp_populate_sysfs(ptp);
if (err)
goto no_sysfs;
/* Register a new PPS source. */
if (info->pps) {
struct pps_source_info pps;
memset(&pps, 0, sizeof(pps));
snprintf(pps.name, PPS_MAX_NAME_LEN, "ptp%d", index);
pps.mode = PTP_PPS_MODE;
pps.owner = info->owner;
ptp->pps_source = pps_register_source(&pps, PTP_PPS_DEFAULTS);
if (!ptp->pps_source) {
pr_err("failed to register pps source\n");
goto no_pps;
}
}
/* Create a posix clock. */
err = posix_clock_register(&ptp->clock, ptp->devid);
if (err) {
pr_err("failed to create posix clock\n");
goto no_clock;
}
return ptp;
no_clock:
if (ptp->pps_source)
pps_unregister_source(ptp->pps_source);
no_pps:
ptp_cleanup_sysfs(ptp);
no_sysfs:
device_destroy(ptp_class, ptp->devid);
no_device:
mutex_destroy(&ptp->tsevq_mux);
mutex_destroy(&ptp->pincfg_mux);
no_slot:
kfree(ptp);
no_memory:
return ERR_PTR(err);
}
EXPORT_SYMBOL(ptp_clock_register);
int ptp_clock_unregister(struct ptp_clock *ptp)
{
ptp->defunct = 1;
wake_up_interruptible(&ptp->tsev_wq);
/* Release the clock's resources. */
if (ptp->pps_source)
pps_unregister_source(ptp->pps_source);
ptp_cleanup_sysfs(ptp);
device_destroy(ptp_class, ptp->devid);
posix_clock_unregister(&ptp->clock);
return 0;
}
EXPORT_SYMBOL(ptp_clock_unregister);
void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
{
struct pps_event_time evt;
switch (event->type) {
case PTP_CLOCK_ALARM:
break;
case PTP_CLOCK_EXTTS:
enqueue_external_timestamp(&ptp->tsevq, event);
wake_up_interruptible(&ptp->tsev_wq);
break;
case PTP_CLOCK_PPS:
pps_get_ts(&evt);
pps_event(ptp->pps_source, &evt, PTP_PPS_EVENT, NULL);
break;
case PTP_CLOCK_PPSUSR:
pps_event(ptp->pps_source, &event->pps_times,
PTP_PPS_EVENT, NULL);
break;
}
}
EXPORT_SYMBOL(ptp_clock_event);
int ptp_clock_index(struct ptp_clock *ptp)
{
return ptp->index;
}
EXPORT_SYMBOL(ptp_clock_index);
int ptp_find_pin(struct ptp_clock *ptp,
enum ptp_pin_function func, unsigned int chan)
{
struct ptp_pin_desc *pin = NULL;
int i;
mutex_lock(&ptp->pincfg_mux);
for (i = 0; i < ptp->info->n_pins; i++) {
if (ptp->info->pin_config[i].func == func &&
ptp->info->pin_config[i].chan == chan) {
pin = &ptp->info->pin_config[i];
break;
}
}
mutex_unlock(&ptp->pincfg_mux);
return pin ? i : -1;
}
EXPORT_SYMBOL(ptp_find_pin);
/* module operations */
static void __exit ptp_exit(void)
{
class_destroy(ptp_class);
unregister_chrdev_region(ptp_devt, MINORMASK + 1);
ida_destroy(&ptp_clocks_map);
}
static int __init ptp_init(void)
{
int err;
ptp_class = class_create(THIS_MODULE, "ptp");
if (IS_ERR(ptp_class)) {
pr_err("ptp: failed to allocate class\n");
return PTR_ERR(ptp_class);
}
err = alloc_chrdev_region(&ptp_devt, 0, MINORMASK + 1, "ptp");
if (err < 0) {
pr_err("ptp: failed to allocate device region\n");
goto no_region;
}
ptp_class->dev_groups = ptp_groups;
pr_info("PTP clock support registered\n");
return 0;
no_region:
class_destroy(ptp_class);
return err;
}
subsys_initcall(ptp_init);
module_exit(ptp_exit);
MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>");
MODULE_DESCRIPTION("PTP clocks support");
MODULE_LICENSE("GPL");

343
drivers/ptp/ptp_ixp46x.c Normal file
View file

@ -0,0 +1,343 @@
/*
* PTP 1588 clock using the IXP46X
*
* Copyright (C) 2010 OMICRON electronics GmbH
*
* 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.
*/
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/ptp_clock_kernel.h>
#include <mach/ixp46x_ts.h>
#define DRIVER "ptp_ixp46x"
#define N_EXT_TS 2
#define MASTER_GPIO 8
#define MASTER_IRQ 25
#define SLAVE_GPIO 7
#define SLAVE_IRQ 24
struct ixp_clock {
struct ixp46x_ts_regs *regs;
struct ptp_clock *ptp_clock;
struct ptp_clock_info caps;
int exts0_enabled;
int exts1_enabled;
};
DEFINE_SPINLOCK(register_lock);
/*
* Register access functions
*/
static u64 ixp_systime_read(struct ixp46x_ts_regs *regs)
{
u64 ns;
u32 lo, hi;
lo = __raw_readl(&regs->systime_lo);
hi = __raw_readl(&regs->systime_hi);
ns = ((u64) hi) << 32;
ns |= lo;
ns <<= TICKS_NS_SHIFT;
return ns;
}
static void ixp_systime_write(struct ixp46x_ts_regs *regs, u64 ns)
{
u32 hi, lo;
ns >>= TICKS_NS_SHIFT;
hi = ns >> 32;
lo = ns & 0xffffffff;
__raw_writel(lo, &regs->systime_lo);
__raw_writel(hi, &regs->systime_hi);
}
/*
* Interrupt service routine
*/
static irqreturn_t isr(int irq, void *priv)
{
struct ixp_clock *ixp_clock = priv;
struct ixp46x_ts_regs *regs = ixp_clock->regs;
struct ptp_clock_event event;
u32 ack = 0, lo, hi, val;
val = __raw_readl(&regs->event);
if (val & TSER_SNS) {
ack |= TSER_SNS;
if (ixp_clock->exts0_enabled) {
hi = __raw_readl(&regs->asms_hi);
lo = __raw_readl(&regs->asms_lo);
event.type = PTP_CLOCK_EXTTS;
event.index = 0;
event.timestamp = ((u64) hi) << 32;
event.timestamp |= lo;
event.timestamp <<= TICKS_NS_SHIFT;
ptp_clock_event(ixp_clock->ptp_clock, &event);
}
}
if (val & TSER_SNM) {
ack |= TSER_SNM;
if (ixp_clock->exts1_enabled) {
hi = __raw_readl(&regs->amms_hi);
lo = __raw_readl(&regs->amms_lo);
event.type = PTP_CLOCK_EXTTS;
event.index = 1;
event.timestamp = ((u64) hi) << 32;
event.timestamp |= lo;
event.timestamp <<= TICKS_NS_SHIFT;
ptp_clock_event(ixp_clock->ptp_clock, &event);
}
}
if (val & TTIPEND)
ack |= TTIPEND; /* this bit seems to be always set */
if (ack) {
__raw_writel(ack, &regs->event);
return IRQ_HANDLED;
} else
return IRQ_NONE;
}
/*
* PTP clock operations
*/
static int ptp_ixp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
{
u64 adj;
u32 diff, addend;
int neg_adj = 0;
struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
struct ixp46x_ts_regs *regs = ixp_clock->regs;
if (ppb < 0) {
neg_adj = 1;
ppb = -ppb;
}
addend = DEFAULT_ADDEND;
adj = addend;
adj *= ppb;
diff = div_u64(adj, 1000000000ULL);
addend = neg_adj ? addend - diff : addend + diff;
__raw_writel(addend, &regs->addend);
return 0;
}
static int ptp_ixp_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
s64 now;
unsigned long flags;
struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
struct ixp46x_ts_regs *regs = ixp_clock->regs;
spin_lock_irqsave(&register_lock, flags);
now = ixp_systime_read(regs);
now += delta;
ixp_systime_write(regs, now);
spin_unlock_irqrestore(&register_lock, flags);
return 0;
}
static int ptp_ixp_gettime(struct ptp_clock_info *ptp, struct timespec *ts)
{
u64 ns;
u32 remainder;
unsigned long flags;
struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
struct ixp46x_ts_regs *regs = ixp_clock->regs;
spin_lock_irqsave(&register_lock, flags);
ns = ixp_systime_read(regs);
spin_unlock_irqrestore(&register_lock, flags);
ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
ts->tv_nsec = remainder;
return 0;
}
static int ptp_ixp_settime(struct ptp_clock_info *ptp,
const struct timespec *ts)
{
u64 ns;
unsigned long flags;
struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
struct ixp46x_ts_regs *regs = ixp_clock->regs;
ns = ts->tv_sec * 1000000000ULL;
ns += ts->tv_nsec;
spin_lock_irqsave(&register_lock, flags);
ixp_systime_write(regs, ns);
spin_unlock_irqrestore(&register_lock, flags);
return 0;
}
static int ptp_ixp_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
struct ixp_clock *ixp_clock = container_of(ptp, struct ixp_clock, caps);
switch (rq->type) {
case PTP_CLK_REQ_EXTTS:
switch (rq->extts.index) {
case 0:
ixp_clock->exts0_enabled = on ? 1 : 0;
break;
case 1:
ixp_clock->exts1_enabled = on ? 1 : 0;
break;
default:
return -EINVAL;
}
return 0;
default:
break;
}
return -EOPNOTSUPP;
}
static struct ptp_clock_info ptp_ixp_caps = {
.owner = THIS_MODULE,
.name = "IXP46X timer",
.max_adj = 66666655,
.n_ext_ts = N_EXT_TS,
.n_pins = 0,
.pps = 0,
.adjfreq = ptp_ixp_adjfreq,
.adjtime = ptp_ixp_adjtime,
.gettime = ptp_ixp_gettime,
.settime = ptp_ixp_settime,
.enable = ptp_ixp_enable,
};
/* module operations */
static struct ixp_clock ixp_clock;
static int setup_interrupt(int gpio)
{
int irq;
int err;
err = gpio_request(gpio, "ixp4-ptp");
if (err)
return err;
err = gpio_direction_input(gpio);
if (err)
return err;
irq = gpio_to_irq(gpio);
if (NO_IRQ == irq)
return NO_IRQ;
if (irq_set_irq_type(irq, IRQF_TRIGGER_FALLING)) {
pr_err("cannot set trigger type for irq %d\n", irq);
return NO_IRQ;
}
if (request_irq(irq, isr, 0, DRIVER, &ixp_clock)) {
pr_err("request_irq failed for irq %d\n", irq);
return NO_IRQ;
}
return irq;
}
static void __exit ptp_ixp_exit(void)
{
free_irq(MASTER_IRQ, &ixp_clock);
free_irq(SLAVE_IRQ, &ixp_clock);
ixp46x_phc_index = -1;
ptp_clock_unregister(ixp_clock.ptp_clock);
}
static int __init ptp_ixp_init(void)
{
if (!cpu_is_ixp46x())
return -ENODEV;
ixp_clock.regs =
(struct ixp46x_ts_regs __iomem *) IXP4XX_TIMESYNC_BASE_VIRT;
ixp_clock.caps = ptp_ixp_caps;
ixp_clock.ptp_clock = ptp_clock_register(&ixp_clock.caps, NULL);
if (IS_ERR(ixp_clock.ptp_clock))
return PTR_ERR(ixp_clock.ptp_clock);
ixp46x_phc_index = ptp_clock_index(ixp_clock.ptp_clock);
__raw_writel(DEFAULT_ADDEND, &ixp_clock.regs->addend);
__raw_writel(1, &ixp_clock.regs->trgt_lo);
__raw_writel(0, &ixp_clock.regs->trgt_hi);
__raw_writel(TTIPEND, &ixp_clock.regs->event);
if (MASTER_IRQ != setup_interrupt(MASTER_GPIO)) {
pr_err("failed to setup gpio %d as irq\n", MASTER_GPIO);
goto no_master;
}
if (SLAVE_IRQ != setup_interrupt(SLAVE_GPIO)) {
pr_err("failed to setup gpio %d as irq\n", SLAVE_GPIO);
goto no_slave;
}
return 0;
no_slave:
free_irq(MASTER_IRQ, &ixp_clock);
no_master:
ptp_clock_unregister(ixp_clock.ptp_clock);
return -ENODEV;
}
module_init(ptp_ixp_init);
module_exit(ptp_ixp_exit);
MODULE_AUTHOR("Richard Cochran <richardcochran@gmail.com>");
MODULE_DESCRIPTION("PTP clock using the IXP46X timer");
MODULE_LICENSE("GPL");

736
drivers/ptp/ptp_pch.c Normal file
View file

@ -0,0 +1,736 @@
/*
* PTP 1588 clock using the EG20T PCH
*
* Copyright (C) 2010 OMICRON electronics GmbH
* Copyright (C) 2011-2012 LAPIS SEMICONDUCTOR Co., LTD.
*
* This code was derived from the IXP46X driver.
*
* 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; version 2 of the License.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/
#include <linux/device.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/slab.h>
#define STATION_ADDR_LEN 20
#define PCI_DEVICE_ID_PCH_1588 0x8819
#define IO_MEM_BAR 1
#define DEFAULT_ADDEND 0xA0000000
#define TICKS_NS_SHIFT 5
#define N_EXT_TS 2
enum pch_status {
PCH_SUCCESS,
PCH_INVALIDPARAM,
PCH_NOTIMESTAMP,
PCH_INTERRUPTMODEINUSE,
PCH_FAILED,
PCH_UNSUPPORTED,
};
/**
* struct pch_ts_regs - IEEE 1588 registers
*/
struct pch_ts_regs {
u32 control;
u32 event;
u32 addend;
u32 accum;
u32 test;
u32 ts_compare;
u32 rsystime_lo;
u32 rsystime_hi;
u32 systime_lo;
u32 systime_hi;
u32 trgt_lo;
u32 trgt_hi;
u32 asms_lo;
u32 asms_hi;
u32 amms_lo;
u32 amms_hi;
u32 ch_control;
u32 ch_event;
u32 tx_snap_lo;
u32 tx_snap_hi;
u32 rx_snap_lo;
u32 rx_snap_hi;
u32 src_uuid_lo;
u32 src_uuid_hi;
u32 can_status;
u32 can_snap_lo;
u32 can_snap_hi;
u32 ts_sel;
u32 ts_st[6];
u32 reserve1[14];
u32 stl_max_set_en;
u32 stl_max_set;
u32 reserve2[13];
u32 srst;
};
#define PCH_TSC_RESET (1 << 0)
#define PCH_TSC_TTM_MASK (1 << 1)
#define PCH_TSC_ASMS_MASK (1 << 2)
#define PCH_TSC_AMMS_MASK (1 << 3)
#define PCH_TSC_PPSM_MASK (1 << 4)
#define PCH_TSE_TTIPEND (1 << 1)
#define PCH_TSE_SNS (1 << 2)
#define PCH_TSE_SNM (1 << 3)
#define PCH_TSE_PPS (1 << 4)
#define PCH_CC_MM (1 << 0)
#define PCH_CC_TA (1 << 1)
#define PCH_CC_MODE_SHIFT 16
#define PCH_CC_MODE_MASK 0x001F0000
#define PCH_CC_VERSION (1 << 31)
#define PCH_CE_TXS (1 << 0)
#define PCH_CE_RXS (1 << 1)
#define PCH_CE_OVR (1 << 0)
#define PCH_CE_VAL (1 << 1)
#define PCH_ECS_ETH (1 << 0)
#define PCH_ECS_CAN (1 << 1)
#define PCH_STATION_BYTES 6
#define PCH_IEEE1588_ETH (1 << 0)
#define PCH_IEEE1588_CAN (1 << 1)
/**
* struct pch_dev - Driver private data
*/
struct pch_dev {
struct pch_ts_regs __iomem *regs;
struct ptp_clock *ptp_clock;
struct ptp_clock_info caps;
int exts0_enabled;
int exts1_enabled;
u32 mem_base;
u32 mem_size;
u32 irq;
struct pci_dev *pdev;
spinlock_t register_lock;
};
/**
* struct pch_params - 1588 module parameter
*/
struct pch_params {
u8 station[STATION_ADDR_LEN];
};
/* structure to hold the module parameters */
static struct pch_params pch_param = {
"00:00:00:00:00:00"
};
/*
* Register access functions
*/
static inline void pch_eth_enable_set(struct pch_dev *chip)
{
u32 val;
/* SET the eth_enable bit */
val = ioread32(&chip->regs->ts_sel) | (PCH_ECS_ETH);
iowrite32(val, (&chip->regs->ts_sel));
}
static u64 pch_systime_read(struct pch_ts_regs __iomem *regs)
{
u64 ns;
u32 lo, hi;
lo = ioread32(&regs->systime_lo);
hi = ioread32(&regs->systime_hi);
ns = ((u64) hi) << 32;
ns |= lo;
ns <<= TICKS_NS_SHIFT;
return ns;
}
static void pch_systime_write(struct pch_ts_regs __iomem *regs, u64 ns)
{
u32 hi, lo;
ns >>= TICKS_NS_SHIFT;
hi = ns >> 32;
lo = ns & 0xffffffff;
iowrite32(lo, &regs->systime_lo);
iowrite32(hi, &regs->systime_hi);
}
static inline void pch_block_reset(struct pch_dev *chip)
{
u32 val;
/* Reset Hardware Assist block */
val = ioread32(&chip->regs->control) | PCH_TSC_RESET;
iowrite32(val, (&chip->regs->control));
val = val & ~PCH_TSC_RESET;
iowrite32(val, (&chip->regs->control));
}
u32 pch_ch_control_read(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
u32 val;
val = ioread32(&chip->regs->ch_control);
return val;
}
EXPORT_SYMBOL(pch_ch_control_read);
void pch_ch_control_write(struct pci_dev *pdev, u32 val)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
iowrite32(val, (&chip->regs->ch_control));
}
EXPORT_SYMBOL(pch_ch_control_write);
u32 pch_ch_event_read(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
u32 val;
val = ioread32(&chip->regs->ch_event);
return val;
}
EXPORT_SYMBOL(pch_ch_event_read);
void pch_ch_event_write(struct pci_dev *pdev, u32 val)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
iowrite32(val, (&chip->regs->ch_event));
}
EXPORT_SYMBOL(pch_ch_event_write);
u32 pch_src_uuid_lo_read(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
u32 val;
val = ioread32(&chip->regs->src_uuid_lo);
return val;
}
EXPORT_SYMBOL(pch_src_uuid_lo_read);
u32 pch_src_uuid_hi_read(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
u32 val;
val = ioread32(&chip->regs->src_uuid_hi);
return val;
}
EXPORT_SYMBOL(pch_src_uuid_hi_read);
u64 pch_rx_snap_read(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
u64 ns;
u32 lo, hi;
lo = ioread32(&chip->regs->rx_snap_lo);
hi = ioread32(&chip->regs->rx_snap_hi);
ns = ((u64) hi) << 32;
ns |= lo;
ns <<= TICKS_NS_SHIFT;
return ns;
}
EXPORT_SYMBOL(pch_rx_snap_read);
u64 pch_tx_snap_read(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
u64 ns;
u32 lo, hi;
lo = ioread32(&chip->regs->tx_snap_lo);
hi = ioread32(&chip->regs->tx_snap_hi);
ns = ((u64) hi) << 32;
ns |= lo;
ns <<= TICKS_NS_SHIFT;
return ns;
}
EXPORT_SYMBOL(pch_tx_snap_read);
/* This function enables all 64 bits in system time registers [high & low].
This is a work-around for non continuous value in the SystemTime Register*/
static void pch_set_system_time_count(struct pch_dev *chip)
{
iowrite32(0x01, &chip->regs->stl_max_set_en);
iowrite32(0xFFFFFFFF, &chip->regs->stl_max_set);
iowrite32(0x00, &chip->regs->stl_max_set_en);
}
static void pch_reset(struct pch_dev *chip)
{
/* Reset Hardware Assist */
pch_block_reset(chip);
/* enable all 32 bits in system time registers */
pch_set_system_time_count(chip);
}
/**
* pch_set_station_address() - This API sets the station address used by
* IEEE 1588 hardware when looking at PTP
* traffic on the ethernet interface
* @addr: dress which contain the column separated address to be used.
*/
int pch_set_station_address(u8 *addr, struct pci_dev *pdev)
{
s32 i;
struct pch_dev *chip = pci_get_drvdata(pdev);
/* Verify the parameter */
if ((chip->regs == NULL) || addr == (u8 *)NULL) {
dev_err(&pdev->dev,
"invalid params returning PCH_INVALIDPARAM\n");
return PCH_INVALIDPARAM;
}
/* For all station address bytes */
for (i = 0; i < PCH_STATION_BYTES; i++) {
u32 val;
s32 tmp;
tmp = hex_to_bin(addr[i * 3]);
if (tmp < 0) {
dev_err(&pdev->dev,
"invalid params returning PCH_INVALIDPARAM\n");
return PCH_INVALIDPARAM;
}
val = tmp * 16;
tmp = hex_to_bin(addr[(i * 3) + 1]);
if (tmp < 0) {
dev_err(&pdev->dev,
"invalid params returning PCH_INVALIDPARAM\n");
return PCH_INVALIDPARAM;
}
val += tmp;
/* Expects ':' separated addresses */
if ((i < 5) && (addr[(i * 3) + 2] != ':')) {
dev_err(&pdev->dev,
"invalid params returning PCH_INVALIDPARAM\n");
return PCH_INVALIDPARAM;
}
/* Ideally we should set the address only after validating
entire string */
dev_dbg(&pdev->dev, "invoking pch_station_set\n");
iowrite32(val, &chip->regs->ts_st[i]);
}
return 0;
}
EXPORT_SYMBOL(pch_set_station_address);
/*
* Interrupt service routine
*/
static irqreturn_t isr(int irq, void *priv)
{
struct pch_dev *pch_dev = priv;
struct pch_ts_regs __iomem *regs = pch_dev->regs;
struct ptp_clock_event event;
u32 ack = 0, lo, hi, val;
val = ioread32(&regs->event);
if (val & PCH_TSE_SNS) {
ack |= PCH_TSE_SNS;
if (pch_dev->exts0_enabled) {
hi = ioread32(&regs->asms_hi);
lo = ioread32(&regs->asms_lo);
event.type = PTP_CLOCK_EXTTS;
event.index = 0;
event.timestamp = ((u64) hi) << 32;
event.timestamp |= lo;
event.timestamp <<= TICKS_NS_SHIFT;
ptp_clock_event(pch_dev->ptp_clock, &event);
}
}
if (val & PCH_TSE_SNM) {
ack |= PCH_TSE_SNM;
if (pch_dev->exts1_enabled) {
hi = ioread32(&regs->amms_hi);
lo = ioread32(&regs->amms_lo);
event.type = PTP_CLOCK_EXTTS;
event.index = 1;
event.timestamp = ((u64) hi) << 32;
event.timestamp |= lo;
event.timestamp <<= TICKS_NS_SHIFT;
ptp_clock_event(pch_dev->ptp_clock, &event);
}
}
if (val & PCH_TSE_TTIPEND)
ack |= PCH_TSE_TTIPEND; /* this bit seems to be always set */
if (ack) {
iowrite32(ack, &regs->event);
return IRQ_HANDLED;
} else
return IRQ_NONE;
}
/*
* PTP clock operations
*/
static int ptp_pch_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
{
u64 adj;
u32 diff, addend;
int neg_adj = 0;
struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps);
struct pch_ts_regs __iomem *regs = pch_dev->regs;
if (ppb < 0) {
neg_adj = 1;
ppb = -ppb;
}
addend = DEFAULT_ADDEND;
adj = addend;
adj *= ppb;
diff = div_u64(adj, 1000000000ULL);
addend = neg_adj ? addend - diff : addend + diff;
iowrite32(addend, &regs->addend);
return 0;
}
static int ptp_pch_adjtime(struct ptp_clock_info *ptp, s64 delta)
{
s64 now;
unsigned long flags;
struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps);
struct pch_ts_regs __iomem *regs = pch_dev->regs;
spin_lock_irqsave(&pch_dev->register_lock, flags);
now = pch_systime_read(regs);
now += delta;
pch_systime_write(regs, now);
spin_unlock_irqrestore(&pch_dev->register_lock, flags);
return 0;
}
static int ptp_pch_gettime(struct ptp_clock_info *ptp, struct timespec *ts)
{
u64 ns;
u32 remainder;
unsigned long flags;
struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps);
struct pch_ts_regs __iomem *regs = pch_dev->regs;
spin_lock_irqsave(&pch_dev->register_lock, flags);
ns = pch_systime_read(regs);
spin_unlock_irqrestore(&pch_dev->register_lock, flags);
ts->tv_sec = div_u64_rem(ns, 1000000000, &remainder);
ts->tv_nsec = remainder;
return 0;
}
static int ptp_pch_settime(struct ptp_clock_info *ptp,
const struct timespec *ts)
{
u64 ns;
unsigned long flags;
struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps);
struct pch_ts_regs __iomem *regs = pch_dev->regs;
ns = ts->tv_sec * 1000000000ULL;
ns += ts->tv_nsec;
spin_lock_irqsave(&pch_dev->register_lock, flags);
pch_systime_write(regs, ns);
spin_unlock_irqrestore(&pch_dev->register_lock, flags);
return 0;
}
static int ptp_pch_enable(struct ptp_clock_info *ptp,
struct ptp_clock_request *rq, int on)
{
struct pch_dev *pch_dev = container_of(ptp, struct pch_dev, caps);
switch (rq->type) {
case PTP_CLK_REQ_EXTTS:
switch (rq->extts.index) {
case 0:
pch_dev->exts0_enabled = on ? 1 : 0;
break;
case 1:
pch_dev->exts1_enabled = on ? 1 : 0;
break;
default:
return -EINVAL;
}
return 0;
default:
break;
}
return -EOPNOTSUPP;
}
static struct ptp_clock_info ptp_pch_caps = {
.owner = THIS_MODULE,
.name = "PCH timer",
.max_adj = 50000000,
.n_ext_ts = N_EXT_TS,
.n_pins = 0,
.pps = 0,
.adjfreq = ptp_pch_adjfreq,
.adjtime = ptp_pch_adjtime,
.gettime = ptp_pch_gettime,
.settime = ptp_pch_settime,
.enable = ptp_pch_enable,
};
#ifdef CONFIG_PM
static s32 pch_suspend(struct pci_dev *pdev, pm_message_t state)
{
pci_disable_device(pdev);
pci_enable_wake(pdev, PCI_D3hot, 0);
if (pci_save_state(pdev) != 0) {
dev_err(&pdev->dev, "could not save PCI config state\n");
return -ENOMEM;
}
pci_set_power_state(pdev, pci_choose_state(pdev, state));
return 0;
}
static s32 pch_resume(struct pci_dev *pdev)
{
s32 ret;
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
ret = pci_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "pci_enable_device failed\n");
return ret;
}
pci_enable_wake(pdev, PCI_D3hot, 0);
return 0;
}
#else
#define pch_suspend NULL
#define pch_resume NULL
#endif
static void pch_remove(struct pci_dev *pdev)
{
struct pch_dev *chip = pci_get_drvdata(pdev);
ptp_clock_unregister(chip->ptp_clock);
/* free the interrupt */
if (pdev->irq != 0)
free_irq(pdev->irq, chip);
/* unmap the virtual IO memory space */
if (chip->regs != NULL) {
iounmap(chip->regs);
chip->regs = NULL;
}
/* release the reserved IO memory space */
if (chip->mem_base != 0) {
release_mem_region(chip->mem_base, chip->mem_size);
chip->mem_base = 0;
}
pci_disable_device(pdev);
kfree(chip);
dev_info(&pdev->dev, "complete\n");
}
static s32
pch_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
s32 ret;
unsigned long flags;
struct pch_dev *chip;
chip = kzalloc(sizeof(struct pch_dev), GFP_KERNEL);
if (chip == NULL)
return -ENOMEM;
/* enable the 1588 pci device */
ret = pci_enable_device(pdev);
if (ret != 0) {
dev_err(&pdev->dev, "could not enable the pci device\n");
goto err_pci_en;
}
chip->mem_base = pci_resource_start(pdev, IO_MEM_BAR);
if (!chip->mem_base) {
dev_err(&pdev->dev, "could not locate IO memory address\n");
ret = -ENODEV;
goto err_pci_start;
}
/* retrieve the available length of the IO memory space */
chip->mem_size = pci_resource_len(pdev, IO_MEM_BAR);
/* allocate the memory for the device registers */
if (!request_mem_region(chip->mem_base, chip->mem_size, "1588_regs")) {
dev_err(&pdev->dev,
"could not allocate register memory space\n");
ret = -EBUSY;
goto err_req_mem_region;
}
/* get the virtual address to the 1588 registers */
chip->regs = ioremap(chip->mem_base, chip->mem_size);
if (!chip->regs) {
dev_err(&pdev->dev, "Could not get virtual address\n");
ret = -ENOMEM;
goto err_ioremap;
}
chip->caps = ptp_pch_caps;
chip->ptp_clock = ptp_clock_register(&chip->caps, &pdev->dev);
if (IS_ERR(chip->ptp_clock)) {
ret = PTR_ERR(chip->ptp_clock);
goto err_ptp_clock_reg;
}
spin_lock_init(&chip->register_lock);
ret = request_irq(pdev->irq, &isr, IRQF_SHARED, KBUILD_MODNAME, chip);
if (ret != 0) {
dev_err(&pdev->dev, "failed to get irq %d\n", pdev->irq);
goto err_req_irq;
}
/* indicate success */
chip->irq = pdev->irq;
chip->pdev = pdev;
pci_set_drvdata(pdev, chip);
spin_lock_irqsave(&chip->register_lock, flags);
/* reset the ieee1588 h/w */
pch_reset(chip);
iowrite32(DEFAULT_ADDEND, &chip->regs->addend);
iowrite32(1, &chip->regs->trgt_lo);
iowrite32(0, &chip->regs->trgt_hi);
iowrite32(PCH_TSE_TTIPEND, &chip->regs->event);
pch_eth_enable_set(chip);
if (strcmp(pch_param.station, "00:00:00:00:00:00") != 0) {
if (pch_set_station_address(pch_param.station, pdev) != 0) {
dev_err(&pdev->dev,
"Invalid station address parameter\n"
"Module loaded but station address not set correctly\n"
);
}
}
spin_unlock_irqrestore(&chip->register_lock, flags);
return 0;
err_req_irq:
ptp_clock_unregister(chip->ptp_clock);
err_ptp_clock_reg:
iounmap(chip->regs);
chip->regs = NULL;
err_ioremap:
release_mem_region(chip->mem_base, chip->mem_size);
err_req_mem_region:
chip->mem_base = 0;
err_pci_start:
pci_disable_device(pdev);
err_pci_en:
kfree(chip);
dev_err(&pdev->dev, "probe failed(ret=0x%x)\n", ret);
return ret;
}
static const struct pci_device_id pch_ieee1588_pcidev_id[] = {
{
.vendor = PCI_VENDOR_ID_INTEL,
.device = PCI_DEVICE_ID_PCH_1588
},
{0}
};
static struct pci_driver pch_driver = {
.name = KBUILD_MODNAME,
.id_table = pch_ieee1588_pcidev_id,
.probe = pch_probe,
.remove = pch_remove,
.suspend = pch_suspend,
.resume = pch_resume,
};
static void __exit ptp_pch_exit(void)
{
pci_unregister_driver(&pch_driver);
}
static s32 __init ptp_pch_init(void)
{
s32 ret;
/* register the driver with the pci core */
ret = pci_register_driver(&pch_driver);
return ret;
}
module_init(ptp_pch_init);
module_exit(ptp_pch_exit);
module_param_string(station,
pch_param.station, sizeof(pch_param.station), 0444);
MODULE_PARM_DESC(station,
"IEEE 1588 station address to use - colon separated hex values");
MODULE_AUTHOR("LAPIS SEMICONDUCTOR, <tshimizu818@gmail.com>");
MODULE_DESCRIPTION("PTP clock using the EG20T timer");
MODULE_LICENSE("GPL");

101
drivers/ptp/ptp_private.h Normal file
View file

@ -0,0 +1,101 @@
/*
* PTP 1588 clock support - private declarations for the core module.
*
* Copyright (C) 2010 OMICRON electronics GmbH
*
* 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.
*/
#ifndef _PTP_PRIVATE_H_
#define _PTP_PRIVATE_H_
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/posix-clock.h>
#include <linux/ptp_clock.h>
#include <linux/ptp_clock_kernel.h>
#include <linux/time.h>
#define PTP_MAX_TIMESTAMPS 128
#define PTP_BUF_TIMESTAMPS 30
struct timestamp_event_queue {
struct ptp_extts_event buf[PTP_MAX_TIMESTAMPS];
int head;
int tail;
spinlock_t lock;
};
struct ptp_clock {
struct posix_clock clock;
struct device *dev;
struct ptp_clock_info *info;
dev_t devid;
int index; /* index into clocks.map */
struct pps_device *pps_source;
long dialed_frequency; /* remembers the frequency adjustment */
struct timestamp_event_queue tsevq; /* simple fifo for time stamps */
struct mutex tsevq_mux; /* one process at a time reading the fifo */
struct mutex pincfg_mux; /* protect concurrent info->pin_config access */
wait_queue_head_t tsev_wq;
int defunct; /* tells readers to go away when clock is being removed */
struct device_attribute *pin_dev_attr;
struct attribute **pin_attr;
struct attribute_group pin_attr_group;
};
/*
* The function queue_cnt() is safe for readers to call without
* holding q->lock. Readers use this function to verify that the queue
* is nonempty before proceeding with a dequeue operation. The fact
* that a writer might concurrently increment the tail does not
* matter, since the queue remains nonempty nonetheless.
*/
static inline int queue_cnt(struct timestamp_event_queue *q)
{
int cnt = q->tail - q->head;
return cnt < 0 ? PTP_MAX_TIMESTAMPS + cnt : cnt;
}
/*
* see ptp_chardev.c
*/
/* caller must hold pincfg_mux */
int ptp_set_pinfunc(struct ptp_clock *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan);
long ptp_ioctl(struct posix_clock *pc,
unsigned int cmd, unsigned long arg);
int ptp_open(struct posix_clock *pc, fmode_t fmode);
ssize_t ptp_read(struct posix_clock *pc,
uint flags, char __user *buf, size_t cnt);
uint ptp_poll(struct posix_clock *pc,
struct file *fp, poll_table *wait);
/*
* see ptp_sysfs.c
*/
extern const struct attribute_group *ptp_groups[];
int ptp_cleanup_sysfs(struct ptp_clock *ptp);
int ptp_populate_sysfs(struct ptp_clock *ptp);
#endif

352
drivers/ptp/ptp_sysfs.c Normal file
View file

@ -0,0 +1,352 @@
/*
* PTP 1588 clock support - sysfs interface.
*
* Copyright (C) 2010 OMICRON electronics GmbH
*
* 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.
*/
#include <linux/capability.h>
#include <linux/slab.h>
#include "ptp_private.h"
static ssize_t clock_name_show(struct device *dev,
struct device_attribute *attr, char *page)
{
struct ptp_clock *ptp = dev_get_drvdata(dev);
return snprintf(page, PAGE_SIZE-1, "%s\n", ptp->info->name);
}
static DEVICE_ATTR(clock_name, 0444, clock_name_show, NULL);
#define PTP_SHOW_INT(name, var) \
static ssize_t var##_show(struct device *dev, \
struct device_attribute *attr, char *page) \
{ \
struct ptp_clock *ptp = dev_get_drvdata(dev); \
return snprintf(page, PAGE_SIZE-1, "%d\n", ptp->info->var); \
} \
static DEVICE_ATTR(name, 0444, var##_show, NULL);
PTP_SHOW_INT(max_adjustment, max_adj);
PTP_SHOW_INT(n_alarms, n_alarm);
PTP_SHOW_INT(n_external_timestamps, n_ext_ts);
PTP_SHOW_INT(n_periodic_outputs, n_per_out);
PTP_SHOW_INT(n_programmable_pins, n_pins);
PTP_SHOW_INT(pps_available, pps);
static struct attribute *ptp_attrs[] = {
&dev_attr_clock_name.attr,
&dev_attr_max_adjustment.attr,
&dev_attr_n_alarms.attr,
&dev_attr_n_external_timestamps.attr,
&dev_attr_n_periodic_outputs.attr,
&dev_attr_n_programmable_pins.attr,
&dev_attr_pps_available.attr,
NULL,
};
static const struct attribute_group ptp_group = {
.attrs = ptp_attrs,
};
const struct attribute_group *ptp_groups[] = {
&ptp_group,
NULL,
};
static ssize_t extts_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_clock *ptp = dev_get_drvdata(dev);
struct ptp_clock_info *ops = ptp->info;
struct ptp_clock_request req = { .type = PTP_CLK_REQ_EXTTS };
int cnt, enable;
int err = -EINVAL;
cnt = sscanf(buf, "%u %d", &req.extts.index, &enable);
if (cnt != 2)
goto out;
if (req.extts.index >= ops->n_ext_ts)
goto out;
err = ops->enable(ops, &req, enable ? 1 : 0);
if (err)
goto out;
return count;
out:
return err;
}
static ssize_t extts_fifo_show(struct device *dev,
struct device_attribute *attr, char *page)
{
struct ptp_clock *ptp = dev_get_drvdata(dev);
struct timestamp_event_queue *queue = &ptp->tsevq;
struct ptp_extts_event event;
unsigned long flags;
size_t qcnt;
int cnt = 0;
memset(&event, 0, sizeof(event));
if (mutex_lock_interruptible(&ptp->tsevq_mux))
return -ERESTARTSYS;
spin_lock_irqsave(&queue->lock, flags);
qcnt = queue_cnt(queue);
if (qcnt) {
event = queue->buf[queue->head];
queue->head = (queue->head + 1) % PTP_MAX_TIMESTAMPS;
}
spin_unlock_irqrestore(&queue->lock, flags);
if (!qcnt)
goto out;
cnt = snprintf(page, PAGE_SIZE, "%u %lld %u\n",
event.index, event.t.sec, event.t.nsec);
out:
mutex_unlock(&ptp->tsevq_mux);
return cnt;
}
static ssize_t period_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_clock *ptp = dev_get_drvdata(dev);
struct ptp_clock_info *ops = ptp->info;
struct ptp_clock_request req = { .type = PTP_CLK_REQ_PEROUT };
int cnt, enable, err = -EINVAL;
cnt = sscanf(buf, "%u %lld %u %lld %u", &req.perout.index,
&req.perout.start.sec, &req.perout.start.nsec,
&req.perout.period.sec, &req.perout.period.nsec);
if (cnt != 5)
goto out;
if (req.perout.index >= ops->n_per_out)
goto out;
enable = req.perout.period.sec || req.perout.period.nsec;
err = ops->enable(ops, &req, enable);
if (err)
goto out;
return count;
out:
return err;
}
static ssize_t pps_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_clock *ptp = dev_get_drvdata(dev);
struct ptp_clock_info *ops = ptp->info;
struct ptp_clock_request req = { .type = PTP_CLK_REQ_PPS };
int cnt, enable;
int err = -EINVAL;
if (!capable(CAP_SYS_TIME))
return -EPERM;
cnt = sscanf(buf, "%d", &enable);
if (cnt != 1)
goto out;
err = ops->enable(ops, &req, enable ? 1 : 0);
if (err)
goto out;
return count;
out:
return err;
}
static int ptp_pin_name2index(struct ptp_clock *ptp, const char *name)
{
int i;
for (i = 0; i < ptp->info->n_pins; i++) {
if (!strcmp(ptp->info->pin_config[i].name, name))
return i;
}
return -1;
}
static ssize_t ptp_pin_show(struct device *dev, struct device_attribute *attr,
char *page)
{
struct ptp_clock *ptp = dev_get_drvdata(dev);
unsigned int func, chan;
int index;
index = ptp_pin_name2index(ptp, attr->attr.name);
if (index < 0)
return -EINVAL;
if (mutex_lock_interruptible(&ptp->pincfg_mux))
return -ERESTARTSYS;
func = ptp->info->pin_config[index].func;
chan = ptp->info->pin_config[index].chan;
mutex_unlock(&ptp->pincfg_mux);
return snprintf(page, PAGE_SIZE, "%u %u\n", func, chan);
}
static ssize_t ptp_pin_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct ptp_clock *ptp = dev_get_drvdata(dev);
unsigned int func, chan;
int cnt, err, index;
cnt = sscanf(buf, "%u %u", &func, &chan);
if (cnt != 2)
return -EINVAL;
index = ptp_pin_name2index(ptp, attr->attr.name);
if (index < 0)
return -EINVAL;
if (mutex_lock_interruptible(&ptp->pincfg_mux))
return -ERESTARTSYS;
err = ptp_set_pinfunc(ptp, index, func, chan);
mutex_unlock(&ptp->pincfg_mux);
if (err)
return err;
return count;
}
static DEVICE_ATTR(extts_enable, 0220, NULL, extts_enable_store);
static DEVICE_ATTR(fifo, 0444, extts_fifo_show, NULL);
static DEVICE_ATTR(period, 0220, NULL, period_store);
static DEVICE_ATTR(pps_enable, 0220, NULL, pps_enable_store);
int ptp_cleanup_sysfs(struct ptp_clock *ptp)
{
struct device *dev = ptp->dev;
struct ptp_clock_info *info = ptp->info;
if (info->n_ext_ts) {
device_remove_file(dev, &dev_attr_extts_enable);
device_remove_file(dev, &dev_attr_fifo);
}
if (info->n_per_out)
device_remove_file(dev, &dev_attr_period);
if (info->pps)
device_remove_file(dev, &dev_attr_pps_enable);
if (info->n_pins) {
sysfs_remove_group(&dev->kobj, &ptp->pin_attr_group);
kfree(ptp->pin_attr);
kfree(ptp->pin_dev_attr);
}
return 0;
}
static int ptp_populate_pins(struct ptp_clock *ptp)
{
struct device *dev = ptp->dev;
struct ptp_clock_info *info = ptp->info;
int err = -ENOMEM, i, n_pins = info->n_pins;
ptp->pin_dev_attr = kzalloc(n_pins * sizeof(*ptp->pin_dev_attr),
GFP_KERNEL);
if (!ptp->pin_dev_attr)
goto no_dev_attr;
ptp->pin_attr = kzalloc((1 + n_pins) * sizeof(struct attribute *),
GFP_KERNEL);
if (!ptp->pin_attr)
goto no_pin_attr;
for (i = 0; i < n_pins; i++) {
struct device_attribute *da = &ptp->pin_dev_attr[i];
sysfs_attr_init(&da->attr);
da->attr.name = info->pin_config[i].name;
da->attr.mode = 0644;
da->show = ptp_pin_show;
da->store = ptp_pin_store;
ptp->pin_attr[i] = &da->attr;
}
ptp->pin_attr_group.name = "pins";
ptp->pin_attr_group.attrs = ptp->pin_attr;
err = sysfs_create_group(&dev->kobj, &ptp->pin_attr_group);
if (err)
goto no_group;
return 0;
no_group:
kfree(ptp->pin_attr);
no_pin_attr:
kfree(ptp->pin_dev_attr);
no_dev_attr:
return err;
}
int ptp_populate_sysfs(struct ptp_clock *ptp)
{
struct device *dev = ptp->dev;
struct ptp_clock_info *info = ptp->info;
int err;
if (info->n_ext_ts) {
err = device_create_file(dev, &dev_attr_extts_enable);
if (err)
goto out1;
err = device_create_file(dev, &dev_attr_fifo);
if (err)
goto out2;
}
if (info->n_per_out) {
err = device_create_file(dev, &dev_attr_period);
if (err)
goto out3;
}
if (info->pps) {
err = device_create_file(dev, &dev_attr_pps_enable);
if (err)
goto out4;
}
if (info->n_pins) {
err = ptp_populate_pins(ptp);
if (err)
goto out5;
}
return 0;
out5:
if (info->pps)
device_remove_file(dev, &dev_attr_pps_enable);
out4:
if (info->n_per_out)
device_remove_file(dev, &dev_attr_period);
out3:
if (info->n_ext_ts)
device_remove_file(dev, &dev_attr_fifo);
out2:
if (info->n_ext_ts)
device_remove_file(dev, &dev_attr_extts_enable);
out1:
return err;
}