diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-20 21:04:47 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-20 21:04:47 -0700 |
commit | 3b59bf081622b6446db77ad06c93fe23677bc533 (patch) | |
tree | 3f4bb5a27c90cc86994a1f6d3c53fbf9208003cb /drivers/ptp/ptp_pch.c | |
parent | e45836fafe157df137a837093037f741ad8f4c90 (diff) | |
parent | bbdb32cb5b73597386913d052165423b9d736145 (diff) | |
download | linux-next-3b59bf081622b6446db77ad06c93fe23677bc533.tar.gz |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next
Pull networking merge from David Miller:
"1) Move ixgbe driver over to purely page based buffering on receive.
From Alexander Duyck.
2) Add receive packet steering support to e1000e, from Bruce Allan.
3) Convert TCP MD5 support over to RCU, from Eric Dumazet.
4) Reduce cpu usage in handling out-of-order TCP packets on modern
systems, also from Eric Dumazet.
5) Support the IP{,V6}_UNICAST_IF socket options, making the wine
folks happy, from Erich Hoover.
6) Support VLAN trunking from guests in hyperv driver, from Haiyang
Zhang.
7) Support byte-queue-limtis in r8169, from Igor Maravic.
8) Outline code intended for IP_RECVTOS in IP_PKTOPTIONS existed but
was never properly implemented, Jiri Benc fixed that.
9) 64-bit statistics support in r8169 and 8139too, from Junchang Wang.
10) Support kernel side dump filtering by ctmark in netfilter
ctnetlink, from Pablo Neira Ayuso.
11) Support byte-queue-limits in gianfar driver, from Paul Gortmaker.
12) Add new peek socket options to assist with socket migration, from
Pavel Emelyanov.
13) Add sch_plug packet scheduler whose queue is controlled by
userland daemons using explicit freeze and release commands. From
Shriram Rajagopalan.
14) Fix FCOE checksum offload handling on transmit, from Yi Zou."
* git://git.kernel.org/pub/scm/linux/kernel/git/davem/net-next: (1846 commits)
Fix pppol2tp getsockname()
Remove printk from rds_sendmsg
ipv6: fix incorrent ipv6 ipsec packet fragment
cpsw: Hook up default ndo_change_mtu.
net: qmi_wwan: fix build error due to cdc-wdm dependecy
netdev: driver: ethernet: Add TI CPSW driver
netdev: driver: ethernet: add cpsw address lookup engine support
phy: add am79c874 PHY support
mlx4_core: fix race on comm channel
bonding: send igmp report for its master
fs_enet: Add MPC5125 FEC support and PHY interface selection
net: bpf_jit: fix BPF_S_LDX_B_MSH compilation
net: update the usage of CHECKSUM_UNNECESSARY
fcoe: use CHECKSUM_UNNECESSARY instead of CHECKSUM_PARTIAL on tx
net: do not do gso for CHECKSUM_UNNECESSARY in netif_needs_gso
ixgbe: Fix issues with SR-IOV loopback when flow control is disabled
net/hyperv: Fix the code handling tx busy
ixgbe: fix namespace issues when FCoE/DCB is not enabled
rtlwifi: Remove unused ETH_ADDR_LEN defines
igbvf: Use ETH_ALEN
...
Fix up fairly trivial conflicts in drivers/isdn/gigaset/interface.c and
drivers/net/usb/{Kconfig,qmi_wwan.c} as per David.
Diffstat (limited to 'drivers/ptp/ptp_pch.c')
-rw-r--r-- | drivers/ptp/ptp_pch.c | 730 |
1 files changed, 730 insertions, 0 deletions
diff --git a/drivers/ptp/ptp_pch.c b/drivers/ptp/ptp_pch.c new file mode 100644 index 000000000000..375eb04c16ea --- /dev/null +++ b/drivers/ptp/ptp_pch.c @@ -0,0 +1,730 @@ +/* + * 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> + +#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 *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 *regs) +{ + u64 ns; + u32 lo, hi; + + lo = ioread32(®s->systime_lo); + hi = ioread32(®s->systime_hi); + + ns = ((u64) hi) << 32; + ns |= lo; + ns <<= TICKS_NS_SHIFT; + + return ns; +} + +static void pch_systime_write(struct pch_ts_regs *regs, u64 ns) +{ + u32 hi, lo; + + ns >>= TICKS_NS_SHIFT; + hi = ns >> 32; + lo = ns & 0xffffffff; + + iowrite32(lo, ®s->systime_lo); + iowrite32(hi, ®s->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; + + 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; + + 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. + */ +static 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 == 0) || 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; +} + +/* + * Interrupt service routine + */ +static irqreturn_t isr(int irq, void *priv) +{ + struct pch_dev *pch_dev = priv; + struct pch_ts_regs *regs = pch_dev->regs; + struct ptp_clock_event event; + u32 ack = 0, lo, hi, val; + + val = ioread32(®s->event); + + if (val & PCH_TSE_SNS) { + ack |= PCH_TSE_SNS; + if (pch_dev->exts0_enabled) { + hi = ioread32(®s->asms_hi); + lo = ioread32(®s->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(®s->amms_hi); + lo = ioread32(®s->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, ®s->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 *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, ®s->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 *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 *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 *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, + .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 __devexit 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 != 0) { + iounmap(chip->regs); + chip->regs = 0; + } + /* 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 __devinit +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); + + if (IS_ERR(chip->ptp_clock)) + return PTR_ERR(chip->ptp_clock); + + 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); + /* Version: IEEE1588 v1 and IEEE1588-2008, Mode: All Evwnt, Locked */ + iowrite32(0x80020000, &chip->regs->ch_control); + + 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); + iounmap(chip->regs); + chip->regs = 0; + +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 DEFINE_PCI_DEVICE_TABLE(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 - column separated hex values"); + +MODULE_AUTHOR("LAPIS SEMICONDUCTOR, <tshimizu818@gmail.com>"); +MODULE_DESCRIPTION("PTP clock using the EG20T timer"); +MODULE_LICENSE("GPL"); |