Commit 6f46fc9b authored by David S. Miller's avatar David S. Miller

Merge branch 'am65x-ptp'

Diogo Ivo says

====================
Enable PTP timestamping/PPS for AM65x SR1.0 devices

This patch series enables support for PTP in AM65x SR1.0 devices.

This feature relies heavily on the Industrial Ethernet Peripheral
(IEP) hardware module, which implements a hardware counter through
which time is kept. This hardware block is the basis for exposing
a PTP hardware clock to userspace and for issuing timestamps for
incoming/outgoing packets, allowing for time synchronization.

The IEP also has compare registers that fire an interrupt when the
counter reaches the value stored in a compare register. This feature
allows us to support PPS events in the kernel.

The changes are separated into five patches:
 - PATCH 01/05: Register SR1.0 devices with the IEP infrastructure to
		expose a PHC clock to userspace, allowing time to be
		adjusted using standard PTP tools. The code for issuing/
		collecting packet timestamps is already present in the
		current state of the driver, so only this needs to be
		done.
 - PATCH 02/05: Remove unnecessary spinlock synchronization.
 - PATCH 03/05: Document IEP interrupt in DT binding.
 - PATCH 04/05: Add support for IEP compare event/interrupt handling
		to enable PPS events.
 - PATCH 05/05: Add the interrupts to the IOT2050 device tree.

Currently every compare event generates two interrupts, the first
corresponding to the actual event and the second being a spurious
but otherwise harmless interrupt. The root cause of this has been
identified and has been solved in the platform's SDK. A forward port
of the SDK's patches also fixes the problem in upstream but is not
included here since it's upstreaming is out of the scope of this
series. If someone from TI would be willing to chime in and help
get the interrupt changes upstream that would be great!
Signed-off-by: default avatarDiogo Ivo <diogo.ivo@siemens.com>
---
Changes in v4:
- Remove unused 'flags' variables in patch 02/05
- Add patch 03/05 describing IEP interrupt in DT binding
- Link to v3: https://lore.kernel.org/r/20240607-iep-v3-0-4824224105bc@siemens.com

Changes in v3:
- Collect Reviewed-by tags
- Add patch 02/04 removing spinlocks from IEP driver
- Use mutex-based synchronization when accessing HW registers
- Link to v2: https://lore.kernel.org/r/20240604-iep-v2-0-ea8e1c0a5686@siemens.com

Changes in v2:
- Collect Reviewed-by tags
- PATCH 01/03: Limit line length to 80 characters
- PATCH 02/03: Proceed with limited functionality if getting IRQ fails,
	       limit line length to 80 characters
- Link to v1: https://lore.kernel.org/r/20240529-iep-v1-0-7273c07592d3@siemens.com
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 9f1f70dd 71be1189
...@@ -28,6 +28,15 @@ properties: ...@@ -28,6 +28,15 @@ properties:
maxItems: 1 maxItems: 1
description: phandle to the IEP source clock description: phandle to the IEP source clock
interrupts:
maxItems: 1
description:
Interrupt specifier for capture/compare IRQ.
interrupt-names:
items:
- const: iep_cap_cmp
required: required:
- compatible - compatible
- reg - reg
......
...@@ -73,3 +73,15 @@ &icssg0_eth { ...@@ -73,3 +73,15 @@ &icssg0_eth {
"rx0", "rx1", "rx0", "rx1",
"rxmgm0", "rxmgm1"; "rxmgm0", "rxmgm1";
}; };
&icssg0_iep0 {
interrupt-parent = <&icssg0_intc>;
interrupts = <7 7 7>;
interrupt-names = "iep_cap_cmp";
};
&icssg0_iep1 {
interrupt-parent = <&icssg0_intc>;
interrupts = <56 8 8>;
interrupt-names = "iep_cap_cmp";
};
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include <linux/timekeeping.h> #include <linux/timekeeping.h>
#include <linux/interrupt.h> #include <linux/interrupt.h>
#include <linux/of_irq.h> #include <linux/of_irq.h>
#include <linux/workqueue.h>
#include "icss_iep.h" #include "icss_iep.h"
...@@ -110,7 +111,6 @@ struct icss_iep { ...@@ -110,7 +111,6 @@ struct icss_iep {
struct ptp_clock_info ptp_info; struct ptp_clock_info ptp_info;
struct ptp_clock *ptp_clock; struct ptp_clock *ptp_clock;
struct mutex ptp_clk_mutex; /* PHC access serializer */ struct mutex ptp_clk_mutex; /* PHC access serializer */
spinlock_t irq_lock; /* CMP IRQ vs icss_iep_ptp_enable access */
u32 def_inc; u32 def_inc;
s16 slow_cmp_inc; s16 slow_cmp_inc;
u32 slow_cmp_count; u32 slow_cmp_count;
...@@ -122,6 +122,7 @@ struct icss_iep { ...@@ -122,6 +122,7 @@ struct icss_iep {
int cap_cmp_irq; int cap_cmp_irq;
u64 period; u64 period;
u32 latch_enable; u32 latch_enable;
struct work_struct work;
}; };
/** /**
...@@ -192,14 +193,11 @@ static void icss_iep_update_to_next_boundary(struct icss_iep *iep, u64 start_ns) ...@@ -192,14 +193,11 @@ static void icss_iep_update_to_next_boundary(struct icss_iep *iep, u64 start_ns)
*/ */
static void icss_iep_settime(struct icss_iep *iep, u64 ns) static void icss_iep_settime(struct icss_iep *iep, u64 ns)
{ {
unsigned long flags;
if (iep->ops && iep->ops->settime) { if (iep->ops && iep->ops->settime) {
iep->ops->settime(iep->clockops_data, ns); iep->ops->settime(iep->clockops_data, ns);
return; return;
} }
spin_lock_irqsave(&iep->irq_lock, flags);
if (iep->pps_enabled || iep->perout_enabled) if (iep->pps_enabled || iep->perout_enabled)
writel(0, iep->base + iep->plat_data->reg_offs[ICSS_IEP_SYNC_CTRL_REG]); writel(0, iep->base + iep->plat_data->reg_offs[ICSS_IEP_SYNC_CTRL_REG]);
...@@ -210,7 +208,6 @@ static void icss_iep_settime(struct icss_iep *iep, u64 ns) ...@@ -210,7 +208,6 @@ static void icss_iep_settime(struct icss_iep *iep, u64 ns)
writel(IEP_SYNC_CTRL_SYNC_N_EN(0) | IEP_SYNC_CTRL_SYNC_EN, writel(IEP_SYNC_CTRL_SYNC_N_EN(0) | IEP_SYNC_CTRL_SYNC_EN,
iep->base + iep->plat_data->reg_offs[ICSS_IEP_SYNC_CTRL_REG]); iep->base + iep->plat_data->reg_offs[ICSS_IEP_SYNC_CTRL_REG]);
} }
spin_unlock_irqrestore(&iep->irq_lock, flags);
} }
/** /**
...@@ -546,7 +543,6 @@ static int icss_iep_perout_enable_hw(struct icss_iep *iep, ...@@ -546,7 +543,6 @@ static int icss_iep_perout_enable_hw(struct icss_iep *iep,
static int icss_iep_perout_enable(struct icss_iep *iep, static int icss_iep_perout_enable(struct icss_iep *iep,
struct ptp_perout_request *req, int on) struct ptp_perout_request *req, int on)
{ {
unsigned long flags;
int ret = 0; int ret = 0;
mutex_lock(&iep->ptp_clk_mutex); mutex_lock(&iep->ptp_clk_mutex);
...@@ -559,11 +555,9 @@ static int icss_iep_perout_enable(struct icss_iep *iep, ...@@ -559,11 +555,9 @@ static int icss_iep_perout_enable(struct icss_iep *iep,
if (iep->perout_enabled == !!on) if (iep->perout_enabled == !!on)
goto exit; goto exit;
spin_lock_irqsave(&iep->irq_lock, flags);
ret = icss_iep_perout_enable_hw(iep, req, on); ret = icss_iep_perout_enable_hw(iep, req, on);
if (!ret) if (!ret)
iep->perout_enabled = !!on; iep->perout_enabled = !!on;
spin_unlock_irqrestore(&iep->irq_lock, flags);
exit: exit:
mutex_unlock(&iep->ptp_clk_mutex); mutex_unlock(&iep->ptp_clk_mutex);
...@@ -571,11 +565,61 @@ static int icss_iep_perout_enable(struct icss_iep *iep, ...@@ -571,11 +565,61 @@ static int icss_iep_perout_enable(struct icss_iep *iep,
return ret; return ret;
} }
static void icss_iep_cap_cmp_work(struct work_struct *work)
{
struct icss_iep *iep = container_of(work, struct icss_iep, work);
const u32 *reg_offs = iep->plat_data->reg_offs;
struct ptp_clock_event pevent;
unsigned int val;
u64 ns, ns_next;
mutex_lock(&iep->ptp_clk_mutex);
ns = readl(iep->base + reg_offs[ICSS_IEP_CMP1_REG0]);
if (iep->plat_data->flags & ICSS_IEP_64BIT_COUNTER_SUPPORT) {
val = readl(iep->base + reg_offs[ICSS_IEP_CMP1_REG1]);
ns |= (u64)val << 32;
}
/* set next event */
ns_next = ns + iep->period;
writel(lower_32_bits(ns_next),
iep->base + reg_offs[ICSS_IEP_CMP1_REG0]);
if (iep->plat_data->flags & ICSS_IEP_64BIT_COUNTER_SUPPORT)
writel(upper_32_bits(ns_next),
iep->base + reg_offs[ICSS_IEP_CMP1_REG1]);
pevent.pps_times.ts_real = ns_to_timespec64(ns);
pevent.type = PTP_CLOCK_PPSUSR;
pevent.index = 0;
ptp_clock_event(iep->ptp_clock, &pevent);
dev_dbg(iep->dev, "IEP:pps ts: %llu next:%llu:\n", ns, ns_next);
mutex_unlock(&iep->ptp_clk_mutex);
}
static irqreturn_t icss_iep_cap_cmp_irq(int irq, void *dev_id)
{
struct icss_iep *iep = (struct icss_iep *)dev_id;
const u32 *reg_offs = iep->plat_data->reg_offs;
unsigned int val;
val = readl(iep->base + reg_offs[ICSS_IEP_CMP_STAT_REG]);
/* The driver only enables CMP1 */
if (val & BIT(1)) {
/* Clear the event */
writel(BIT(1), iep->base + reg_offs[ICSS_IEP_CMP_STAT_REG]);
if (iep->pps_enabled || iep->perout_enabled)
schedule_work(&iep->work);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int icss_iep_pps_enable(struct icss_iep *iep, int on) static int icss_iep_pps_enable(struct icss_iep *iep, int on)
{ {
struct ptp_clock_request rq; struct ptp_clock_request rq;
struct timespec64 ts; struct timespec64 ts;
unsigned long flags;
int ret = 0; int ret = 0;
u64 ns; u64 ns;
...@@ -589,8 +633,6 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) ...@@ -589,8 +633,6 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on)
if (iep->pps_enabled == !!on) if (iep->pps_enabled == !!on)
goto exit; goto exit;
spin_lock_irqsave(&iep->irq_lock, flags);
rq.perout.index = 0; rq.perout.index = 0;
if (on) { if (on) {
ns = icss_iep_gettime(iep, NULL); ns = icss_iep_gettime(iep, NULL);
...@@ -602,13 +644,13 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on) ...@@ -602,13 +644,13 @@ static int icss_iep_pps_enable(struct icss_iep *iep, int on)
ret = icss_iep_perout_enable_hw(iep, &rq.perout, on); ret = icss_iep_perout_enable_hw(iep, &rq.perout, on);
} else { } else {
ret = icss_iep_perout_enable_hw(iep, &rq.perout, on); ret = icss_iep_perout_enable_hw(iep, &rq.perout, on);
if (iep->cap_cmp_irq)
cancel_work_sync(&iep->work);
} }
if (!ret) if (!ret)
iep->pps_enabled = !!on; iep->pps_enabled = !!on;
spin_unlock_irqrestore(&iep->irq_lock, flags);
exit: exit:
mutex_unlock(&iep->ptp_clk_mutex); mutex_unlock(&iep->ptp_clk_mutex);
...@@ -777,6 +819,8 @@ int icss_iep_init(struct icss_iep *iep, const struct icss_iep_clockops *clkops, ...@@ -777,6 +819,8 @@ int icss_iep_init(struct icss_iep *iep, const struct icss_iep_clockops *clkops,
if (iep->ops && iep->ops->perout_enable) { if (iep->ops && iep->ops->perout_enable) {
iep->ptp_info.n_per_out = 1; iep->ptp_info.n_per_out = 1;
iep->ptp_info.pps = 1; iep->ptp_info.pps = 1;
} else if (iep->cap_cmp_irq) {
iep->ptp_info.pps = 1;
} }
if (iep->ops && iep->ops->extts_enable) if (iep->ops && iep->ops->extts_enable)
...@@ -817,6 +861,7 @@ static int icss_iep_probe(struct platform_device *pdev) ...@@ -817,6 +861,7 @@ static int icss_iep_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev; struct device *dev = &pdev->dev;
struct icss_iep *iep; struct icss_iep *iep;
struct clk *iep_clk; struct clk *iep_clk;
int ret, irq;
iep = devm_kzalloc(dev, sizeof(*iep), GFP_KERNEL); iep = devm_kzalloc(dev, sizeof(*iep), GFP_KERNEL);
if (!iep) if (!iep)
...@@ -827,6 +872,22 @@ static int icss_iep_probe(struct platform_device *pdev) ...@@ -827,6 +872,22 @@ static int icss_iep_probe(struct platform_device *pdev)
if (IS_ERR(iep->base)) if (IS_ERR(iep->base))
return -ENODEV; return -ENODEV;
irq = platform_get_irq_byname_optional(pdev, "iep_cap_cmp");
if (irq == -EPROBE_DEFER)
return irq;
if (irq > 0) {
ret = devm_request_irq(dev, irq, icss_iep_cap_cmp_irq,
IRQF_TRIGGER_HIGH, "iep_cap_cmp", iep);
if (ret) {
dev_info(iep->dev, "cap_cmp irq request failed: %x\n",
ret);
} else {
iep->cap_cmp_irq = irq;
INIT_WORK(&iep->work, icss_iep_cap_cmp_work);
}
}
iep_clk = devm_clk_get(dev, NULL); iep_clk = devm_clk_get(dev, NULL);
if (IS_ERR(iep_clk)) if (IS_ERR(iep_clk))
return PTR_ERR(iep_clk); return PTR_ERR(iep_clk);
...@@ -853,7 +914,6 @@ static int icss_iep_probe(struct platform_device *pdev) ...@@ -853,7 +914,6 @@ static int icss_iep_probe(struct platform_device *pdev)
iep->ptp_info = icss_iep_ptp_info; iep->ptp_info = icss_iep_ptp_info;
mutex_init(&iep->ptp_clk_mutex); mutex_init(&iep->ptp_clk_mutex);
spin_lock_init(&iep->irq_lock);
dev_set_drvdata(dev, iep); dev_set_drvdata(dev, iep);
icss_iep_disable(iep); icss_iep_disable(iep);
......
...@@ -1011,16 +1011,44 @@ static int prueth_probe(struct platform_device *pdev) ...@@ -1011,16 +1011,44 @@ static int prueth_probe(struct platform_device *pdev)
dev_dbg(dev, "sram: pa %llx va %p size %zx\n", prueth->msmcram.pa, dev_dbg(dev, "sram: pa %llx va %p size %zx\n", prueth->msmcram.pa,
prueth->msmcram.va, prueth->msmcram.size); prueth->msmcram.va, prueth->msmcram.size);
prueth->iep0 = icss_iep_get_idx(np, 0);
if (IS_ERR(prueth->iep0)) {
ret = dev_err_probe(dev, PTR_ERR(prueth->iep0),
"iep0 get failed\n");
goto free_pool;
}
prueth->iep1 = icss_iep_get_idx(np, 1);
if (IS_ERR(prueth->iep1)) {
ret = dev_err_probe(dev, PTR_ERR(prueth->iep1),
"iep1 get failed\n");
goto put_iep0;
}
ret = icss_iep_init(prueth->iep0, NULL, NULL, 0);
if (ret) {
dev_err_probe(dev, ret, "failed to init iep0\n");
goto put_iep;
}
ret = icss_iep_init(prueth->iep1, NULL, NULL, 0);
if (ret) {
dev_err_probe(dev, ret, "failed to init iep1\n");
goto exit_iep0;
}
if (eth0_node) { if (eth0_node) {
ret = prueth_netdev_init(prueth, eth0_node); ret = prueth_netdev_init(prueth, eth0_node);
if (ret) { if (ret) {
dev_err_probe(dev, ret, "netdev init %s failed\n", dev_err_probe(dev, ret, "netdev init %s failed\n",
eth0_node->name); eth0_node->name);
goto free_pool; goto exit_iep;
} }
if (of_find_property(eth0_node, "ti,half-duplex-capable", NULL)) if (of_find_property(eth0_node, "ti,half-duplex-capable", NULL))
prueth->emac[PRUETH_MAC0]->half_duplex = 1; prueth->emac[PRUETH_MAC0]->half_duplex = 1;
prueth->emac[PRUETH_MAC0]->iep = prueth->iep0;
} }
if (eth1_node) { if (eth1_node) {
...@@ -1033,6 +1061,8 @@ static int prueth_probe(struct platform_device *pdev) ...@@ -1033,6 +1061,8 @@ static int prueth_probe(struct platform_device *pdev)
if (of_find_property(eth1_node, "ti,half-duplex-capable", NULL)) if (of_find_property(eth1_node, "ti,half-duplex-capable", NULL))
prueth->emac[PRUETH_MAC1]->half_duplex = 1; prueth->emac[PRUETH_MAC1]->half_duplex = 1;
prueth->emac[PRUETH_MAC1]->iep = prueth->iep1;
} }
/* register the network devices */ /* register the network devices */
...@@ -1091,6 +1121,19 @@ static int prueth_probe(struct platform_device *pdev) ...@@ -1091,6 +1121,19 @@ static int prueth_probe(struct platform_device *pdev)
prueth_netdev_exit(prueth, eth_node); prueth_netdev_exit(prueth, eth_node);
} }
exit_iep:
icss_iep_exit(prueth->iep1);
exit_iep0:
icss_iep_exit(prueth->iep0);
put_iep:
icss_iep_put(prueth->iep1);
put_iep0:
icss_iep_put(prueth->iep0);
prueth->iep0 = NULL;
prueth->iep1 = NULL;
free_pool: free_pool:
gen_pool_free(prueth->sram_pool, gen_pool_free(prueth->sram_pool,
(unsigned long)prueth->msmcram.va, msmc_ram_size); (unsigned long)prueth->msmcram.va, msmc_ram_size);
...@@ -1138,6 +1181,12 @@ static void prueth_remove(struct platform_device *pdev) ...@@ -1138,6 +1181,12 @@ static void prueth_remove(struct platform_device *pdev)
prueth_netdev_exit(prueth, eth_node); prueth_netdev_exit(prueth, eth_node);
} }
icss_iep_exit(prueth->iep1);
icss_iep_exit(prueth->iep0);
icss_iep_put(prueth->iep1);
icss_iep_put(prueth->iep0);
gen_pool_free(prueth->sram_pool, gen_pool_free(prueth->sram_pool,
(unsigned long)prueth->msmcram.va, (unsigned long)prueth->msmcram.va,
MSMC_RAM_SIZE_SR1); MSMC_RAM_SIZE_SR1);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment