diff options
Diffstat (limited to 'drivers/net/ethernet/xscale')
-rw-r--r-- | drivers/net/ethernet/xscale/Kconfig | 14 | ||||
-rw-r--r-- | drivers/net/ethernet/xscale/Makefile | 3 | ||||
-rw-r--r-- | drivers/net/ethernet/xscale/ixp46x_ts.h | 68 | ||||
-rw-r--r-- | drivers/net/ethernet/xscale/ixp4xx_eth.c | 213 | ||||
-rw-r--r-- | drivers/net/ethernet/xscale/ptp_ixp46x.c | 329 |
5 files changed, 516 insertions, 111 deletions
diff --git a/drivers/net/ethernet/xscale/Kconfig b/drivers/net/ethernet/xscale/Kconfig index cd0a8f46e7c6..98aa7b8ddb06 100644 --- a/drivers/net/ethernet/xscale/Kconfig +++ b/drivers/net/ethernet/xscale/Kconfig @@ -27,4 +27,18 @@ config IXP4XX_ETH Say Y here if you want to use built-in Ethernet ports on IXP4xx processor. +config PTP_1588_CLOCK_IXP46X + tristate "Intel IXP46x as PTP clock" + depends on IXP4XX_ETH + depends on 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. + endif # NET_VENDOR_XSCALE diff --git a/drivers/net/ethernet/xscale/Makefile b/drivers/net/ethernet/xscale/Makefile index 794a519d07b3..607f91b1e878 100644 --- a/drivers/net/ethernet/xscale/Makefile +++ b/drivers/net/ethernet/xscale/Makefile @@ -3,4 +3,5 @@ # Makefile for the Intel XScale IXP device drivers. # -obj-$(CONFIG_IXP4XX_ETH) += ixp4xx_eth.o +obj-$(CONFIG_IXP4XX_ETH) += ixp4xx_eth.o +obj-$(CONFIG_PTP_1588_CLOCK_IXP46X) += ptp_ixp46x.o diff --git a/drivers/net/ethernet/xscale/ixp46x_ts.h b/drivers/net/ethernet/xscale/ixp46x_ts.h new file mode 100644 index 000000000000..d792130e27b0 --- /dev/null +++ b/drivers/net/ethernet/xscale/ixp46x_ts.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * PTP 1588 clock using the IXP46X + * + * Copyright (C) 2010 OMICRON electronics GmbH + */ + +#ifndef _IXP46X_TS_H_ +#define _IXP46X_TS_H_ + +#define DEFAULT_ADDEND 0xF0000029 +#define TICKS_NS_SHIFT 4 + +struct ixp46x_channel_ctl { + u32 ch_control; /* 0x40 Time Synchronization Channel Control */ + u32 ch_event; /* 0x44 Time Synchronization Channel Event */ + u32 tx_snap_lo; /* 0x48 Transmit Snapshot Low Register */ + u32 tx_snap_hi; /* 0x4C Transmit Snapshot High Register */ + u32 rx_snap_lo; /* 0x50 Receive Snapshot Low Register */ + u32 rx_snap_hi; /* 0x54 Receive Snapshot High Register */ + u32 src_uuid_lo; /* 0x58 Source UUID0 Low Register */ + u32 src_uuid_hi; /* 0x5C Sequence Identifier/Source UUID0 High */ +}; + +struct ixp46x_ts_regs { + u32 control; /* 0x00 Time Sync Control Register */ + u32 event; /* 0x04 Time Sync Event Register */ + u32 addend; /* 0x08 Time Sync Addend Register */ + u32 accum; /* 0x0C Time Sync Accumulator Register */ + u32 test; /* 0x10 Time Sync Test Register */ + u32 unused; /* 0x14 */ + u32 rsystime_lo; /* 0x18 RawSystemTime_Low Register */ + u32 rsystime_hi; /* 0x1C RawSystemTime_High Register */ + u32 systime_lo; /* 0x20 SystemTime_Low Register */ + u32 systime_hi; /* 0x24 SystemTime_High Register */ + u32 trgt_lo; /* 0x28 TargetTime_Low Register */ + u32 trgt_hi; /* 0x2C TargetTime_High Register */ + u32 asms_lo; /* 0x30 Auxiliary Slave Mode Snapshot Low */ + u32 asms_hi; /* 0x34 Auxiliary Slave Mode Snapshot High */ + u32 amms_lo; /* 0x38 Auxiliary Master Mode Snapshot Low */ + u32 amms_hi; /* 0x3C Auxiliary Master Mode Snapshot High */ + + struct ixp46x_channel_ctl channel[3]; +}; + +/* 0x00 Time Sync Control Register Bits */ +#define TSCR_AMM (1<<3) +#define TSCR_ASM (1<<2) +#define TSCR_TTM (1<<1) +#define TSCR_RST (1<<0) + +/* 0x04 Time Sync Event Register Bits */ +#define TSER_SNM (1<<3) +#define TSER_SNS (1<<2) +#define TTIPEND (1<<1) + +/* 0x40 Time Synchronization Channel Control Register Bits */ +#define MASTER_MODE (1<<0) +#define TIMESTAMP_ALL (1<<1) + +/* 0x44 Time Synchronization Channel Event Register Bits */ +#define TX_SNAPSHOT_LOCKED (1<<0) +#define RX_SNAPSHOT_LOCKED (1<<1) + +/* The ptp_ixp46x module will set this variable */ +extern int ixp46x_phc_index; + +#endif diff --git a/drivers/net/ethernet/xscale/ixp4xx_eth.c b/drivers/net/ethernet/xscale/ixp4xx_eth.c index 6fc04ffb22c2..269596c15133 100644 --- a/drivers/net/ethernet/xscale/ixp4xx_eth.c +++ b/drivers/net/ethernet/xscale/ixp4xx_eth.c @@ -29,14 +29,16 @@ #include <linux/net_tstamp.h> #include <linux/of.h> #include <linux/phy.h> +#include <linux/platform_data/eth_ixp4xx.h> #include <linux/platform_device.h> #include <linux/ptp_classify.h> #include <linux/slab.h> #include <linux/module.h> -#include <mach/ixp46x_ts.h> #include <linux/soc/ixp4xx/npe.h> #include <linux/soc/ixp4xx/qmgr.h> +#include "ixp46x_ts.h" + #define DEBUG_DESC 0 #define DEBUG_RX 0 #define DEBUG_TX 0 @@ -517,25 +519,14 @@ static int ixp4xx_mdio_write(struct mii_bus *bus, int phy_id, int location, return ret; } -static int ixp4xx_mdio_register(void) +static int ixp4xx_mdio_register(struct eth_regs __iomem *regs) { int err; if (!(mdio_bus = mdiobus_alloc())) return -ENOMEM; - if (cpu_is_ixp43x()) { - /* IXP43x lacks NPE-B and uses NPE-C for MII PHY access */ - if (!(ixp4xx_read_feature_bits() & IXP4XX_FEATURE_NPEC_ETH)) - return -ENODEV; - mdio_regs = (struct eth_regs __iomem *)IXP4XX_EthC_BASE_VIRT; - } else { - /* All MII PHY accesses use NPE-B Ethernet registers */ - if (!(ixp4xx_read_feature_bits() & IXP4XX_FEATURE_NPEB_ETH0)) - return -ENODEV; - mdio_regs = (struct eth_regs __iomem *)IXP4XX_EthB_BASE_VIRT; - } - + mdio_regs = regs; __raw_writel(DEFAULT_CORE_CNTRL, &mdio_regs->core_control); spin_lock_init(&mdio_lock); mdio_bus->name = "IXP4xx MII Bus"; @@ -581,8 +572,8 @@ static void ixp4xx_adjust_link(struct net_device *dev) __raw_writel(DEFAULT_TX_CNTRL0 | TX_CNTRL0_HALFDUPLEX, &port->regs->tx_control[0]); - printk(KERN_INFO "%s: link up, speed %u Mb/s, %s duplex\n", - dev->name, port->speed, port->duplex ? "full" : "half"); + netdev_info(dev, "%s: link up, speed %u Mb/s, %s duplex\n", + dev->name, port->speed, port->duplex ? "full" : "half"); } @@ -592,7 +583,7 @@ static inline void debug_pkt(struct net_device *dev, const char *func, #if DEBUG_PKT_BYTES int i; - printk(KERN_DEBUG "%s: %s(%i) ", dev->name, func, len); + netdev_debug(dev, "%s(%i) ", func, len); for (i = 0; i < len; i++) { if (i >= DEBUG_PKT_BYTES) break; @@ -683,7 +674,7 @@ static int eth_poll(struct napi_struct *napi, int budget) int received = 0; #if DEBUG_RX - printk(KERN_DEBUG "%s: eth_poll\n", dev->name); + netdev_debug(dev, "eth_poll\n"); #endif while (received < budget) { @@ -697,23 +688,20 @@ static int eth_poll(struct napi_struct *napi, int budget) if ((n = queue_get_desc(rxq, port, 0)) < 0) { #if DEBUG_RX - printk(KERN_DEBUG "%s: eth_poll napi_complete\n", - dev->name); + netdev_debug(dev, "eth_poll napi_complete\n"); #endif napi_complete(napi); qmgr_enable_irq(rxq); if (!qmgr_stat_below_low_watermark(rxq) && napi_reschedule(napi)) { /* not empty again */ #if DEBUG_RX - printk(KERN_DEBUG "%s: eth_poll napi_reschedule succeeded\n", - dev->name); + netdev_debug(dev, "eth_poll napi_reschedule succeeded\n"); #endif qmgr_disable_irq(rxq); continue; } #if DEBUG_RX - printk(KERN_DEBUG "%s: eth_poll all done\n", - dev->name); + netdev_debug(dev, "eth_poll all done\n"); #endif return received; /* all work done */ } @@ -778,7 +766,7 @@ static int eth_poll(struct napi_struct *napi, int budget) } #if DEBUG_RX - printk(KERN_DEBUG "eth_poll(): end, not all work done\n"); + netdev_debug(dev, "eth_poll(): end, not all work done\n"); #endif return received; /* not all work done */ } @@ -842,7 +830,7 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev) struct desc *desc; #if DEBUG_TX - printk(KERN_DEBUG "%s: eth_xmit\n", dev->name); + netdev_debug(dev, "eth_xmit\n"); #endif if (unlikely(skb->len > MAX_MRU)) { @@ -897,22 +885,21 @@ static int eth_xmit(struct sk_buff *skb, struct net_device *dev) if (qmgr_stat_below_low_watermark(txreadyq)) { /* empty */ #if DEBUG_TX - printk(KERN_DEBUG "%s: eth_xmit queue full\n", dev->name); + netdev_debug(dev, "eth_xmit queue full\n"); #endif netif_stop_queue(dev); /* we could miss TX ready interrupt */ /* really empty in fact */ if (!qmgr_stat_below_low_watermark(txreadyq)) { #if DEBUG_TX - printk(KERN_DEBUG "%s: eth_xmit ready again\n", - dev->name); + netdev_debug(dev, "eth_xmit ready again\n"); #endif netif_wake_queue(dev); } } #if DEBUG_TX - printk(KERN_DEBUG "%s: eth_xmit end\n", dev->name); + netdev_debug(dev, "eth_xmit end\n"); #endif ixp_tx_timestamp(port, skb); @@ -1099,7 +1086,7 @@ static int init_queues(struct port *port) int i; if (!ports_open) { - dma_pool = dma_pool_create(DRV_NAME, &port->netdev->dev, + dma_pool = dma_pool_create(DRV_NAME, port->netdev->dev.parent, POOL_ALLOC_SIZE, 32, 0); if (!dma_pool) return -ENOMEM; @@ -1186,8 +1173,7 @@ static int eth_open(struct net_device *dev) return err; if (npe_recv_message(npe, &msg, "ETH_GET_STATUS")) { - printk(KERN_ERR "%s: %s not responding\n", dev->name, - npe_name(npe)); + netdev_err(dev, "%s not responding\n", npe_name(npe)); return -EIO; } port->firmware[0] = msg.byte4; @@ -1299,7 +1285,7 @@ static int eth_close(struct net_device *dev) msg.eth_id = port->id; msg.byte3 = 1; if (npe_send_recv_message(port->npe, &msg, "ETH_ENABLE_LOOPBACK")) - printk(KERN_CRIT "%s: unable to enable loopback\n", dev->name); + netdev_crit(dev, "unable to enable loopback\n"); i = 0; do { /* drain RX buffers */ @@ -1323,11 +1309,11 @@ static int eth_close(struct net_device *dev) } while (++i < MAX_CLOSE_WAIT); if (buffs) - printk(KERN_CRIT "%s: unable to drain RX queue, %i buffer(s)" - " left in NPE\n", dev->name, buffs); + netdev_crit(dev, "unable to drain RX queue, %i buffer(s)" + " left in NPE\n", buffs); #if DEBUG_CLOSE if (!buffs) - printk(KERN_DEBUG "Draining RX queue took %i cycles\n", i); + netdev_debug(dev, "draining RX queue took %i cycles\n", i); #endif buffs = TX_DESCS; @@ -1343,17 +1329,16 @@ static int eth_close(struct net_device *dev) } while (++i < MAX_CLOSE_WAIT); if (buffs) - printk(KERN_CRIT "%s: unable to drain TX queue, %i buffer(s) " - "left in NPE\n", dev->name, buffs); + netdev_crit(dev, "unable to drain TX queue, %i buffer(s) " + "left in NPE\n", buffs); #if DEBUG_CLOSE if (!buffs) - printk(KERN_DEBUG "Draining TX queues took %i cycles\n", i); + netdev_debug(dev, "draining TX queues took %i cycles\n", i); #endif msg.byte3 = 0; if (npe_send_recv_message(port->npe, &msg, "ETH_DISABLE_LOOPBACK")) - printk(KERN_CRIT "%s: unable to disable loopback\n", - dev->name); + netdev_crit(dev, "unable to disable loopback\n"); phy_stop(dev->phydev); @@ -1374,54 +1359,88 @@ static const struct net_device_ops ixp4xx_netdev_ops = { .ndo_validate_addr = eth_validate_addr, }; -static int eth_init_one(struct platform_device *pdev) +static int ixp4xx_eth_probe(struct platform_device *pdev) { - struct port *port; - struct net_device *dev; - struct eth_plat_info *plat = dev_get_platdata(&pdev->dev); - struct phy_device *phydev = NULL; - u32 regs_phys; char phy_id[MII_BUS_ID_SIZE + 3]; + struct phy_device *phydev = NULL; + struct device *dev = &pdev->dev; + struct eth_plat_info *plat; + resource_size_t regs_phys; + struct net_device *ndev; + struct resource *res; + struct port *port; int err; - if (!(dev = alloc_etherdev(sizeof(struct port)))) + plat = dev_get_platdata(dev); + + if (!(ndev = devm_alloc_etherdev(dev, sizeof(struct port)))) return -ENOMEM; - SET_NETDEV_DEV(dev, &pdev->dev); - port = netdev_priv(dev); - port->netdev = dev; + SET_NETDEV_DEV(ndev, dev); + port = netdev_priv(ndev); + port->netdev = ndev; port->id = pdev->id; + /* Get the port resource and remap */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + regs_phys = res->start; + port->regs = devm_ioremap_resource(dev, res); + switch (port->id) { case IXP4XX_ETH_NPEA: - port->regs = (struct eth_regs __iomem *)IXP4XX_EthA_BASE_VIRT; - regs_phys = IXP4XX_EthA_BASE_PHYS; + /* If the MDIO bus is not up yet, defer probe */ + if (!mdio_bus) + return -EPROBE_DEFER; break; case IXP4XX_ETH_NPEB: - port->regs = (struct eth_regs __iomem *)IXP4XX_EthB_BASE_VIRT; - regs_phys = IXP4XX_EthB_BASE_PHYS; + /* + * On all except IXP43x, NPE-B is used for the MDIO bus. + * If there is no NPE-B in the feature set, bail out, else + * register the MDIO bus. + */ + if (!cpu_is_ixp43x()) { + if (!(ixp4xx_read_feature_bits() & + IXP4XX_FEATURE_NPEB_ETH0)) + return -ENODEV; + /* Else register the MDIO bus on NPE-B */ + if ((err = ixp4xx_mdio_register(port->regs))) + return err; + } + if (!mdio_bus) + return -EPROBE_DEFER; break; case IXP4XX_ETH_NPEC: - port->regs = (struct eth_regs __iomem *)IXP4XX_EthC_BASE_VIRT; - regs_phys = IXP4XX_EthC_BASE_PHYS; + /* + * IXP43x lacks NPE-B and uses NPE-C for the MDIO bus access, + * of there is no NPE-C, no bus, nothing works, so bail out. + */ + if (cpu_is_ixp43x()) { + if (!(ixp4xx_read_feature_bits() & + IXP4XX_FEATURE_NPEC_ETH)) + return -ENODEV; + /* Else register the MDIO bus on NPE-C */ + if ((err = ixp4xx_mdio_register(port->regs))) + return err; + } + if (!mdio_bus) + return -EPROBE_DEFER; break; default: - err = -ENODEV; - goto err_free; + return -ENODEV; } - dev->netdev_ops = &ixp4xx_netdev_ops; - dev->ethtool_ops = &ixp4xx_ethtool_ops; - dev->tx_queue_len = 100; + ndev->netdev_ops = &ixp4xx_netdev_ops; + ndev->ethtool_ops = &ixp4xx_ethtool_ops; + ndev->tx_queue_len = 100; - netif_napi_add(dev, &port->napi, eth_poll, NAPI_WEIGHT); + netif_napi_add(ndev, &port->napi, eth_poll, NAPI_WEIGHT); - if (!(port->npe = npe_request(NPE_ID(port->id)))) { - err = -EIO; - goto err_free; - } + if (!(port->npe = npe_request(NPE_ID(port->id)))) + return -EIO; - port->mem_res = request_mem_region(regs_phys, REGS_SIZE, dev->name); + port->mem_res = request_mem_region(regs_phys, REGS_SIZE, ndev->name); if (!port->mem_res) { err = -EBUSY; goto err_npe_rel; @@ -1429,9 +1448,9 @@ static int eth_init_one(struct platform_device *pdev) port->plat = plat; npe_port_tab[NPE_ID(port->id)] = port; - memcpy(dev->dev_addr, plat->hwaddr, ETH_ALEN); + memcpy(ndev->dev_addr, plat->hwaddr, ETH_ALEN); - platform_set_drvdata(pdev, dev); + platform_set_drvdata(pdev, ndev); __raw_writel(DEFAULT_CORE_CNTRL | CORE_RESET, &port->regs->core_control); @@ -1441,7 +1460,7 @@ static int eth_init_one(struct platform_device *pdev) snprintf(phy_id, MII_BUS_ID_SIZE + 3, PHY_ID_FMT, mdio_bus->id, plat->phy); - phydev = phy_connect(dev, phy_id, &ixp4xx_adjust_link, + phydev = phy_connect(ndev, phy_id, &ixp4xx_adjust_link, PHY_INTERFACE_MODE_MII); if (IS_ERR(phydev)) { err = PTR_ERR(phydev); @@ -1450,11 +1469,11 @@ static int eth_init_one(struct platform_device *pdev) phydev->irq = PHY_POLL; - if ((err = register_netdev(dev))) + if ((err = register_netdev(ndev))) goto err_phy_dis; - printk(KERN_INFO "%s: MII PHY %i on %s\n", dev->name, plat->phy, - npe_name(port->npe)); + netdev_info(ndev, "%s: MII PHY %i on %s\n", ndev->name, plat->phy, + npe_name(port->npe)); return 0; @@ -1465,58 +1484,32 @@ err_free_mem: release_resource(port->mem_res); err_npe_rel: npe_release(port->npe); -err_free: - free_netdev(dev); return err; } -static int eth_remove_one(struct platform_device *pdev) +static int ixp4xx_eth_remove(struct platform_device *pdev) { - struct net_device *dev = platform_get_drvdata(pdev); - struct phy_device *phydev = dev->phydev; - struct port *port = netdev_priv(dev); + struct net_device *ndev = platform_get_drvdata(pdev); + struct phy_device *phydev = ndev->phydev; + struct port *port = netdev_priv(ndev); - unregister_netdev(dev); + unregister_netdev(ndev); phy_disconnect(phydev); + ixp4xx_mdio_remove(); npe_port_tab[NPE_ID(port->id)] = NULL; npe_release(port->npe); release_resource(port->mem_res); - free_netdev(dev); return 0; } static struct platform_driver ixp4xx_eth_driver = { .driver.name = DRV_NAME, - .probe = eth_init_one, - .remove = eth_remove_one, + .probe = ixp4xx_eth_probe, + .remove = ixp4xx_eth_remove, }; - -static int __init eth_init_module(void) -{ - int err; - - /* - * FIXME: we bail out on device tree boot but this really needs - * to be fixed in a nicer way: this registers the MDIO bus before - * even matching the driver infrastructure, we should only probe - * detected hardware. - */ - if (of_have_populated_dt()) - return -ENODEV; - if ((err = ixp4xx_mdio_register())) - return err; - return platform_driver_register(&ixp4xx_eth_driver); -} - -static void __exit eth_cleanup_module(void) -{ - platform_driver_unregister(&ixp4xx_eth_driver); - ixp4xx_mdio_remove(); -} +module_platform_driver(ixp4xx_eth_driver); MODULE_AUTHOR("Krzysztof Halasa"); MODULE_DESCRIPTION("Intel IXP4xx Ethernet driver"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:ixp4xx_eth"); -module_init(eth_init_module); -module_exit(eth_cleanup_module); diff --git a/drivers/net/ethernet/xscale/ptp_ixp46x.c b/drivers/net/ethernet/xscale/ptp_ixp46x.c new file mode 100644 index 000000000000..9ecc395239e9 --- /dev/null +++ b/drivers/net/ethernet/xscale/ptp_ixp46x.c @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * PTP 1588 clock using the IXP46X + * + * Copyright (C) 2010 OMICRON electronics GmbH + */ +#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 "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(®s->systime_lo); + hi = __raw_readl(®s->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, ®s->systime_lo); + __raw_writel(hi, ®s->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(®s->event); + + if (val & TSER_SNS) { + ack |= TSER_SNS; + if (ixp_clock->exts0_enabled) { + hi = __raw_readl(®s->asms_hi); + lo = __raw_readl(®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(ixp_clock->ptp_clock, &event); + } + } + + if (val & TSER_SNM) { + ack |= TSER_SNM; + if (ixp_clock->exts1_enabled) { + hi = __raw_readl(®s->amms_hi); + lo = __raw_readl(®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(ixp_clock->ptp_clock, &event); + } + } + + if (val & TTIPEND) + ack |= TTIPEND; /* this bit seems to be always set */ + + if (ack) { + __raw_writel(ack, ®s->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, ®s->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(®ister_lock, flags); + + now = ixp_systime_read(regs); + now += delta; + ixp_systime_write(regs, now); + + spin_unlock_irqrestore(®ister_lock, flags); + + return 0; +} + +static int ptp_ixp_gettime(struct ptp_clock_info *ptp, struct timespec64 *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; + + spin_lock_irqsave(®ister_lock, flags); + + ns = ixp_systime_read(regs); + + spin_unlock_irqrestore(®ister_lock, flags); + + *ts = ns_to_timespec64(ns); + return 0; +} + +static int ptp_ixp_settime(struct ptp_clock_info *ptp, + const struct timespec64 *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 = timespec64_to_ns(ts); + + spin_lock_irqsave(®ister_lock, flags); + + ixp_systime_write(regs, ns); + + spin_unlock_irqrestore(®ister_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 const 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, + .gettime64 = ptp_ixp_gettime, + .settime64 = 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 (irq < 0) + return irq; + + err = irq_set_irq_type(irq, IRQF_TRIGGER_FALLING); + if (err) { + pr_err("cannot set trigger type for irq %d\n", irq); + return err; + } + + err = request_irq(irq, isr, 0, DRIVER, &ixp_clock); + if (err) { + pr_err("request_irq failed for irq %d\n", irq); + return err; + } + + 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"); |