From: Sagi Maimon <maimon.sagi@gmail.com>
To: jonathan.lemon@gmail.com, vadim.fedorenko@linux.dev,
richardcochran@gmail.com, andrew+netdev@lunn.ch,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com
Cc: linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
Sagi Maimon <maimon.sagi@gmail.com>
Subject: [PATCH v3] ptp: ocp: add CPLD ISP support for ADVA TimeCard
Date: Wed, 1 Jul 2026 10:22:03 +0300 [thread overview]
Message-ID: <20260701072203.4321-1-maimon.sagi@gmail.com> (raw)
The ADVA TimeCard uses a Lattice MachXO3 CPLD that is programmed over
I2C using in-system programming (ISP).
The CPLD is connected to a secondary I2C bus controlled by the onboard
MicroBlaze. Add support for taking ownership of this bus and exposing
the required interfaces through sysfs, allowing userspace tools to
perform CPLD programming.
To limit the scope of this functionality, sysfs-based I2C access is
available only on ADVA TimeCard boards and only for the CPLD-related
I2C slave addresses used during ISP.
Add sysfs support to:
- control ownership of the secondary I2C bus
- access the CPLD-related I2C slave addresses required for ISP
Signed-off-by: Sagi Maimon <maimon.sagi@gmail.com>
---
Addressed comments from:
- Vadim Fedorenko : https://lore.kernel.org/all/c6aff5f7-e087-4bd9-b159-7adeb82e19f4@linux.dev/
Changes since version 2:
- Allow I2C access via sysfs only on ADVA TimeCard boards.
- Allow access only to the ADVA TimeCard-specific I2C slave
addresses.
drivers/ptp/ptp_ocp.c | 180 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 179 insertions(+), 1 deletion(-)
diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c
index 35e911f1ad78..86c341ea4062 100644
--- a/drivers/ptp/ptp_ocp.c
+++ b/drivers/ptp/ptp_ocp.c
@@ -416,6 +416,10 @@ struct ptp_ocp {
dpll_tracker tracker;
int signals_nr;
int freq_in_nr;
+ /* cpld_i2c_xfer sysfs (adva_x1) */
+ struct mutex tap_i2c_lock;
+ u8 tap_i2c_rsp[21]; /* [status, read_data...] */
+ size_t tap_i2c_rsp_len;
};
#define OCP_REQ_TIMESTAMP BIT(0)
@@ -3188,6 +3192,8 @@ ptp_ocp_adva_board_init(struct ptp_ocp *bp, struct ocp_resource *r)
ptp_ocp_nmea_out_init(bp);
ptp_ocp_signal_init(bp);
+ mutex_init(&bp->tap_i2c_lock);
+
err = ptp_ocp_attr_group_add(bp, info->attr_groups);
if (err)
return err;
@@ -4224,6 +4230,171 @@ static const struct ocp_attr_group art_timecard_groups[] = {
{ },
};
+static ssize_t
+i2c_bus_ctrl_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+
+ if (!bp->pps_select)
+ return -ENODEV;
+ return sysfs_emit(buf, "0x%08x\n",
+ ioread32(&bp->pps_select->__pad1));
+}
+
+static ssize_t
+i2c_bus_ctrl_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(dev);
+ u32 val;
+
+ if (!bp->pps_select)
+ return -ENODEV;
+ if (kstrtou32(buf, 0, &val))
+ return -EINVAL;
+ iowrite32(val, &bp->pps_select->__pad1);
+ return count;
+}
+
+static DEVICE_ATTR_RW(i2c_bus_ctrl);
+
+/*
+ * cpld_i2c_xfer - sysfs binary I2C passthrough for adva_x1 TAP CPLD.
+ *
+ * write: [addr][write_len][read_len][flags][write_data...]
+ * flags bit 0: I2C_M_NOSTART on the read segment
+ * read: [status][read_data...]
+ * status 0 = success, else positive errno
+ *
+ * Only addresses 0x40 (CPLD) and 0x74 (mux) are permitted.
+ */
+#define TAP_I2C_ALLOWED_ADDRS_NUM 2
+static const u8 tap_i2c_allowed_addrs[TAP_I2C_ALLOWED_ADDRS_NUM] = {
+ 0x40, /* CPLD */
+ 0x74, /* mux */
+};
+
+#define TAP_I2C_REQ_HDR_LEN 4
+#define TAP_I2C_MAX_WRITE_LEN 67
+#define TAP_I2C_MAX_READ_LEN 20
+#define TAP_I2C_FLAG_NOSTART BIT(0)
+
+static int
+ptp_ocp_tap_i2c_find_adapter(struct device *dev, void *data)
+{
+ struct i2c_adapter **adap = data;
+
+ *adap = i2c_verify_adapter(dev);
+ return (*adap) ? 1 : 0;
+}
+
+static ssize_t
+ptp_ocp_cpld_i2c_write(struct file *file, struct kobject *kobj,
+ const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj));
+ const u8 *req = (const u8 *)buf;
+ u8 addr, write_len, read_len, flags;
+ struct i2c_adapter *adap = NULL;
+ struct i2c_msg msgs[2];
+ u8 rdbuf[TAP_I2C_MAX_READ_LEN];
+ int nmsgs, ret, i;
+
+ if (count < TAP_I2C_REQ_HDR_LEN || count > TAP_I2C_REQ_HDR_LEN + TAP_I2C_MAX_WRITE_LEN)
+ return -EINVAL;
+
+ addr = req[0];
+ write_len = req[1];
+ read_len = req[2];
+ flags = req[3];
+
+ /* Validate */
+ for (i = 0; i < TAP_I2C_ALLOWED_ADDRS_NUM; i++)
+ if (addr == tap_i2c_allowed_addrs[i])
+ break;
+ if (i == TAP_I2C_ALLOWED_ADDRS_NUM)
+ return -EPERM;
+
+ if (write_len > TAP_I2C_MAX_WRITE_LEN)
+ return -EINVAL;
+ if (read_len > TAP_I2C_MAX_READ_LEN)
+ return -EINVAL;
+ if (write_len + TAP_I2C_REQ_HDR_LEN > count)
+ return -EINVAL;
+ if (write_len == 0 && read_len == 0)
+ return -EINVAL;
+
+ if (!bp->i2c_ctrl)
+ return -ENODEV;
+ device_for_each_child(&bp->i2c_ctrl->dev, &adap,
+ ptp_ocp_tap_i2c_find_adapter);
+ if (!adap)
+ return -ENODEV;
+
+ nmsgs = 0;
+ if (write_len > 0) {
+ msgs[nmsgs].addr = addr;
+ msgs[nmsgs].flags = 0;
+ msgs[nmsgs].len = write_len;
+ msgs[nmsgs].buf = (u8 *)req + TAP_I2C_REQ_HDR_LEN;
+ nmsgs++;
+ }
+ if (read_len > 0) {
+ u16 rd_flags = I2C_M_RD;
+
+ if (flags & TAP_I2C_FLAG_NOSTART)
+ rd_flags |= I2C_M_NOSTART;
+ msgs[nmsgs].addr = addr;
+ msgs[nmsgs].flags = rd_flags;
+ msgs[nmsgs].len = read_len;
+ msgs[nmsgs].buf = rdbuf;
+ nmsgs++;
+ }
+
+ ret = i2c_transfer(adap, msgs, nmsgs);
+
+ mutex_lock(&bp->tap_i2c_lock);
+ if (ret == nmsgs) {
+ bp->tap_i2c_rsp[0] = 0;
+ if (read_len > 0)
+ memcpy(&bp->tap_i2c_rsp[1], rdbuf, read_len);
+ bp->tap_i2c_rsp_len = 1 + read_len;
+ ret = count;
+ } else {
+ bp->tap_i2c_rsp[0] = (u8)(ret < 0 ? -ret : EIO);
+ bp->tap_i2c_rsp_len = 1;
+ ret = (ret < 0) ? ret : -EIO;
+ }
+ mutex_unlock(&bp->tap_i2c_lock);
+
+ return ret;
+}
+
+static ssize_t
+ptp_ocp_cpld_i2c_read(struct file *file, struct kobject *kobj,
+ const struct bin_attribute *attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct ptp_ocp *bp = dev_get_drvdata(kobj_to_dev(kobj));
+ ssize_t ret;
+
+ if (off > 0)
+ return 0;
+
+ mutex_lock(&bp->tap_i2c_lock);
+ ret = min(count, bp->tap_i2c_rsp_len);
+ memcpy(buf, bp->tap_i2c_rsp, ret);
+ mutex_unlock(&bp->tap_i2c_lock);
+ return ret;
+}
+
+static const struct bin_attribute tap_i2c_bin_attr = {
+ .attr = { .name = "cpld_i2c_xfer", .mode = 0600 },
+ .write = ptp_ocp_cpld_i2c_write,
+ .read = ptp_ocp_cpld_i2c_read,
+};
+
static struct attribute *adva_timecard_attrs[] = {
&dev_attr_serialnum.attr,
&dev_attr_gnss_sync.attr,
@@ -4272,11 +4443,18 @@ static struct attribute *adva_timecard_x1_attrs[] = {
&dev_attr_ts_window_adjust.attr,
&dev_attr_utc_tai_offset.attr,
&dev_attr_tod_correction.attr,
+ &dev_attr_i2c_bus_ctrl.attr,
+ NULL,
+};
+
+static const struct bin_attribute *const bin_adva_x1_timecard_attrs[] = {
+ &tap_i2c_bin_attr,
NULL,
};
static const struct attribute_group adva_timecard_x1_group = {
- .attrs = adva_timecard_x1_attrs,
+ .attrs = adva_timecard_x1_attrs,
+ .bin_attrs = bin_adva_x1_timecard_attrs,
};
static const struct ocp_attr_group adva_timecard_x1_groups[] = {
--
2.47.0
reply other threads:[~2026-07-01 7:22 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260701072203.4321-1-maimon.sagi@gmail.com \
--to=maimon.sagi@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=jonathan.lemon@gmail.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=richardcochran@gmail.com \
--cc=vadim.fedorenko@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox