diff options
author | David S. Miller <davem@davemloft.net> | 2022-03-11 11:54:45 +0000 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2022-03-11 11:54:45 +0000 |
commit | 59d5923536ac8640f4ff20d011a4851a3c143764 (patch) | |
tree | 04a3cb719086c948c7f3f7fca79d320c6e6389ea | |
parent | fb9eb027fbc90e21cbda18379d85f8603e1f28b9 (diff) | |
parent | ff1d56cb2653e673859bc6233b71762de13c067a (diff) | |
download | lwn-59d5923536ac8640f4ff20d011a4851a3c143764.tar.gz lwn-59d5923536ac8640f4ff20d011a4851a3c143764.zip |
Merge branch 'ptp-ocp-new-firmware-support'
Jonathan Lemon says:
====================
ptp: ocp: support for new firmware
This series contains support for new firmware features for
the timecard.
v1 -> v2: roundup() is not 32-bit safe, use DIV_ROUND_UP_ULL
====================
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | Documentation/ABI/testing/sysfs-timecard | 94 | ||||
-rw-r--r-- | drivers/ptp/ptp_ocp.c | 1206 |
2 files changed, 1144 insertions, 156 deletions
diff --git a/Documentation/ABI/testing/sysfs-timecard b/Documentation/ABI/testing/sysfs-timecard index 5bf78486a469..220478156297 100644 --- a/Documentation/ABI/testing/sysfs-timecard +++ b/Documentation/ABI/testing/sysfs-timecard @@ -37,8 +37,15 @@ Description: (RO) Set of available destinations (sinks) for a SMA PPS2 signal is sent to the PPS2 selector TS1 signal is sent to timestamper 1 TS2 signal is sent to timestamper 2 + TS3 signal is sent to timestamper 3 + TS4 signal is sent to timestamper 4 IRIG signal is sent to the IRIG-B module DCF signal is sent to the DCF module + FREQ1 signal is sent to frequency counter 1 + FREQ2 signal is sent to frequency counter 2 + FREQ3 signal is sent to frequency counter 3 + FREQ4 signal is sent to frequency counter 4 + None signal input is disabled ===== ================================================ What: /sys/class/timecard/ocpN/available_sma_outputs @@ -50,10 +57,16 @@ Description: (RO) Set of available sources for a SMA output signal. 10Mhz output is from the 10Mhz reference clock PHC output PPS is from the PHC clock MAC output PPS is from the Miniature Atomic Clock - GNSS output PPS is from the GNSS module + GNSS1 output PPS is from the first GNSS module GNSS2 output PPS is from the second GNSS module IRIG output is from the PHC, in IRIG-B format DCF output is from the PHC, in DCF format + GEN1 output is from frequency generator 1 + GEN2 output is from frequency generator 2 + GEN3 output is from frequency generator 3 + GEN4 output is from frequency generator 4 + GND output is GND + VCC output is VCC ===== ================================================ What: /sys/class/timecard/ocpN/clock_source @@ -75,6 +88,85 @@ Contact: Jonathan Lemon <jonathan.lemon@gmail.com> Description: (RO) Contains the current offset value used by the firmware for internal disciplining of the atomic clock. +What: /sys/class/timecard/ocpN/freqX +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Optional directory containing the sysfs nodes for + frequency counter <X>. + +What: /sys/class/timecard/ocpN/freqX/frequency +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Contains the measured frequency over the specified + measurement period. + +What: /sys/class/timecard/ocpN/freqX/seconds +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RW) Specifies the number of seconds from 0-255 that the + frequency should be measured over. Write 0 to disable. + +What: /sys/class/timecard/ocpN/genX +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Optional directory containing the sysfs nodes for + frequency generator <X>. + +What: /sys/class/timecard/ocpN/genX/duty +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Specifies the signal duty cycle as a percentage from 1-99. + +What: /sys/class/timecard/ocpN/genX/period +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Specifies the signal period in nanoseconds. + +What: /sys/class/timecard/ocpN/genX/phase +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Specifies the signal phase offset in nanoseconds. + +What: /sys/class/timecard/ocpN/genX/polarity +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Specifies the signal polarity, either 1 or 0. + +What: /sys/class/timecard/ocpN/genX/running +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Either 0 or 1, showing if the signal generator is running. + +What: /sys/class/timecard/ocpN/genX/start +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RO) Shows the time in <sec>.<nsec> that the signal generator + started running. + +What: /sys/class/timecard/ocpN/genX/signal +Date: March 2022 +Contact: Jonathan Lemon <jonathan.lemon@gmail.com> +Description: (RW) Used to start the signal generator, and summarize + the current status. + + The signal generator may be started by writing the signal + period, followed by the optional signal values. If the + optional values are not provided, they default to the current + settings, which may be obtained from the other sysfs nodes. + + period [duty [phase [polarity]]] + + echo 500000000 > signal # 1/2 second period + echo 1000000 40 100 > signal + echo 0 > signal # turn off generator + + Period and phase are specified in nanoseconds. Duty cycle is + a percentage from 1-99. Polarity is 1 or 0. + + Reading this node will return: + + period duty phase polarity start_time + What: /sys/class/timecard/ocpN/gnss_sync Date: September 2021 Contact: Jonathan Lemon <jonathan.lemon@gmail.com> diff --git a/drivers/ptp/ptp_ocp.c b/drivers/ptp/ptp_ocp.c index 653286e01b90..56b04a7bba3a 100644 --- a/drivers/ptp/ptp_ocp.c +++ b/drivers/ptp/ptp_ocp.c @@ -179,6 +179,35 @@ struct dcf_slave_reg { #define DCF_S_CTRL_ENABLE BIT(0) +struct signal_reg { + u32 enable; + u32 status; + u32 polarity; + u32 version; + u32 __pad0[4]; + u32 cable_delay; + u32 __pad1[3]; + u32 intr; + u32 intr_mask; + u32 __pad2[2]; + u32 start_ns; + u32 start_sec; + u32 pulse_ns; + u32 pulse_sec; + u32 period_ns; + u32 period_sec; + u32 repeat_count; +}; + +struct frequency_reg { + u32 ctrl; + u32 status; +}; +#define FREQ_STATUS_VALID BIT(31) +#define FREQ_STATUS_ERROR BIT(30) +#define FREQ_STATUS_OVERRUN BIT(29) +#define FREQ_STATUS_MASK (BIT(24) - 1) + struct ptp_ocp_flash_info { const char *name; int pci_offset; @@ -206,6 +235,37 @@ struct ptp_ocp_ext_src { int irq_vec; }; +enum ptp_ocp_sma_mode { + SMA_MODE_IN, + SMA_MODE_OUT, +}; + +struct ptp_ocp_sma_connector { + enum ptp_ocp_sma_mode mode; + bool fixed_fcn; + bool fixed_dir; + bool disabled; +}; + +struct ocp_attr_group { + u64 cap; + const struct attribute_group *group; +}; + +#define OCP_CAP_BASIC BIT(0) +#define OCP_CAP_SIGNAL BIT(1) +#define OCP_CAP_FREQ BIT(2) + +struct ptp_ocp_signal { + ktime_t period; + ktime_t pulse; + ktime_t phase; + ktime_t start; + int duty; + bool polarity; + bool running; +}; + #define OCP_BOARD_ID_LEN 13 #define OCP_SERIAL_LEN 6 @@ -218,16 +278,21 @@ struct ptp_ocp { struct pps_reg __iomem *pps_to_ext; struct pps_reg __iomem *pps_to_clk; struct gpio_reg __iomem *pps_select; - struct gpio_reg __iomem *sma; + struct gpio_reg __iomem *sma_map1; + struct gpio_reg __iomem *sma_map2; struct irig_master_reg __iomem *irig_out; struct irig_slave_reg __iomem *irig_in; struct dcf_master_reg __iomem *dcf_out; struct dcf_slave_reg __iomem *dcf_in; struct tod_reg __iomem *nmea_out; + struct frequency_reg __iomem *freq_in[4]; + struct ptp_ocp_ext_src *signal_out[4]; struct ptp_ocp_ext_src *pps; struct ptp_ocp_ext_src *ts0; struct ptp_ocp_ext_src *ts1; struct ptp_ocp_ext_src *ts2; + struct ptp_ocp_ext_src *ts3; + struct ptp_ocp_ext_src *ts4; struct img_reg __iomem *image; struct ptp_clock *ptp; struct ptp_clock_info ptp_info; @@ -235,6 +300,7 @@ struct ptp_ocp { struct platform_device *spi_flash; struct clk_hw *i2c_clk; struct timer_list watchdog; + const struct ocp_attr_group *attr_tbl; const struct ptp_ocp_eeprom_map *eeprom_map; struct dentry *debug_root; time64_t gnss_lost; @@ -252,6 +318,9 @@ struct ptp_ocp { int flash_start; u32 utc_tai_offset; u32 ts_window_adjust; + u64 fw_cap; + struct ptp_ocp_signal signal[4]; + struct ptp_ocp_sma_connector sma[4]; }; #define OCP_REQ_TIMESTAMP BIT(0) @@ -274,7 +343,14 @@ static int ptp_ocp_register_serial(struct ptp_ocp *bp, struct ocp_resource *r); static int ptp_ocp_register_ext(struct ptp_ocp *bp, struct ocp_resource *r); static int ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r); static irqreturn_t ptp_ocp_ts_irq(int irq, void *priv); +static irqreturn_t ptp_ocp_signal_irq(int irq, void *priv); static int ptp_ocp_ts_enable(void *priv, u32 req, bool enable); +static int ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen, + struct ptp_perout_request *req); +static int ptp_ocp_signal_enable(void *priv, u32 req, bool enable); +static int ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr); + +static const struct ocp_attr_group fb_timecard_groups[]; struct ptp_ocp_eeprom_map { u16 off; @@ -322,10 +398,10 @@ static struct ptp_ocp_eeprom_map fb_eeprom_map[] = { OCP_RES_LOCATION(member), .setup = ptp_ocp_register_ext /* This is the MSI vector mapping used. - * 0: TS3 (and PPS) + * 0: PPS (TS5) * 1: TS0 * 2: TS1 - * 3: GNSS + * 3: GNSS1 * 4: GNSS2 * 5: MAC * 6: TS2 @@ -333,6 +409,12 @@ static struct ptp_ocp_eeprom_map fb_eeprom_map[] = { * 8: HWICAP (notused) * 9: SPI Flash * 10: NMEA + * 11: Signal Generator 1 + * 12: Signal Generator 2 + * 13: Signal Generator 3 + * 14: Signal Generator 4 + * 15: TS3 + * 16: TS4 */ static struct ocp_resource ocp_fb_resource[] = { @@ -368,15 +450,70 @@ static struct ocp_resource ocp_fb_resource[] = { }, }, { + OCP_EXT_RESOURCE(ts3), + .offset = 0x01110000, .size = 0x10000, .irq_vec = 15, + .extra = &(struct ptp_ocp_ext_info) { + .index = 3, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + { + OCP_EXT_RESOURCE(ts4), + .offset = 0x01120000, .size = 0x10000, .irq_vec = 16, + .extra = &(struct ptp_ocp_ext_info) { + .index = 4, + .irq_fcn = ptp_ocp_ts_irq, + .enable = ptp_ocp_ts_enable, + }, + }, + /* Timestamp for PHC and/or PPS generator */ + { OCP_EXT_RESOURCE(pps), .offset = 0x010C0000, .size = 0x10000, .irq_vec = 0, .extra = &(struct ptp_ocp_ext_info) { - .index = 3, + .index = 5, .irq_fcn = ptp_ocp_ts_irq, .enable = ptp_ocp_ts_enable, }, }, { + OCP_EXT_RESOURCE(signal_out[0]), + .offset = 0x010D0000, .size = 0x10000, .irq_vec = 11, + .extra = &(struct ptp_ocp_ext_info) { + .index = 1, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[1]), + .offset = 0x010E0000, .size = 0x10000, .irq_vec = 12, + .extra = &(struct ptp_ocp_ext_info) { + .index = 2, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[2]), + .offset = 0x010F0000, .size = 0x10000, .irq_vec = 13, + .extra = &(struct ptp_ocp_ext_info) { + .index = 3, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { + OCP_EXT_RESOURCE(signal_out[3]), + .offset = 0x01100000, .size = 0x10000, .irq_vec = 14, + .extra = &(struct ptp_ocp_ext_info) { + .index = 4, + .irq_fcn = ptp_ocp_signal_irq, + .enable = ptp_ocp_signal_enable, + }, + }, + { OCP_MEM_RESOURCE(pps_to_ext), .offset = 0x01030000, .size = 0x10000, }, @@ -417,10 +554,14 @@ static struct ocp_resource ocp_fb_resource[] = { .offset = 0x00130000, .size = 0x1000, }, { - OCP_MEM_RESOURCE(sma), + OCP_MEM_RESOURCE(sma_map1), .offset = 0x00140000, .size = 0x1000, }, { + OCP_MEM_RESOURCE(sma_map2), + .offset = 0x00220000, .size = 0x1000, + }, + { OCP_I2C_RESOURCE(i2c_ctrl), .offset = 0x00150000, .size = 0x10000, .irq_vec = 7, .extra = &(struct ptp_ocp_i2c_info) { @@ -470,6 +611,22 @@ static struct ocp_resource ocp_fb_resource[] = { }, }, { + OCP_MEM_RESOURCE(freq_in[0]), + .offset = 0x01200000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[1]), + .offset = 0x01210000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[2]), + .offset = 0x01220000, .size = 0x10000, + }, + { + OCP_MEM_RESOURCE(freq_in[3]), + .offset = 0x01230000, .size = 0x10000, + }, + { .setup = ptp_ocp_fb_board_init, }, { } @@ -502,25 +659,42 @@ static struct ocp_selector ptp_ocp_clock[] = { { } }; +#define SMA_ENABLE BIT(15) +#define SMA_SELECT_MASK ((1U << 15) - 1) +#define SMA_DISABLE 0x10000 + static struct ocp_selector ptp_ocp_sma_in[] = { - { .name = "10Mhz", .value = 0x00 }, - { .name = "PPS1", .value = 0x01 }, - { .name = "PPS2", .value = 0x02 }, - { .name = "TS1", .value = 0x04 }, - { .name = "TS2", .value = 0x08 }, - { .name = "IRIG", .value = 0x10 }, - { .name = "DCF", .value = 0x20 }, + { .name = "10Mhz", .value = 0x0000 }, + { .name = "PPS1", .value = 0x0001 }, + { .name = "PPS2", .value = 0x0002 }, + { .name = "TS1", .value = 0x0004 }, + { .name = "TS2", .value = 0x0008 }, + { .name = "IRIG", .value = 0x0010 }, + { .name = "DCF", .value = 0x0020 }, + { .name = "TS3", .value = 0x0040 }, + { .name = "TS4", .value = 0x0080 }, + { .name = "FREQ1", .value = 0x0100 }, + { .name = "FREQ2", .value = 0x0200 }, + { .name = "FREQ3", .value = 0x0400 }, + { .name = "FREQ4", .value = 0x0800 }, + { .name = "None", .value = SMA_DISABLE }, { } }; static struct ocp_selector ptp_ocp_sma_out[] = { - { .name = "10Mhz", .value = 0x00 }, - { .name = "PHC", .value = 0x01 }, - { .name = "MAC", .value = 0x02 }, - { .name = "GNSS", .value = 0x04 }, - { .name = "GNSS2", .value = 0x08 }, - { .name = "IRIG", .value = 0x10 }, - { .name = "DCF", .value = 0x20 }, + { .name = "10Mhz", .value = 0x0000 }, + { .name = "PHC", .value = 0x0001 }, + { .name = "MAC", .value = 0x0002 }, + { .name = "GNSS1", .value = 0x0004 }, + { .name = "GNSS2", .value = 0x0008 }, + { .name = "IRIG", .value = 0x0010 }, + { .name = "DCF", .value = 0x0020 }, + { .name = "GEN1", .value = 0x0040 }, + { .name = "GEN2", .value = 0x0080 }, + { .name = "GEN3", .value = 0x0100 }, + { .name = "GEN4", .value = 0x0200 }, + { .name = "GND", .value = 0x2000 }, + { .name = "VCC", .value = 0x4000 }, { } }; @@ -742,6 +916,12 @@ ptp_ocp_enable(struct ptp_clock_info *ptp_info, struct ptp_clock_request *rq, ext = bp->ts2; break; case 3: + ext = bp->ts3; + break; + case 4: + ext = bp->ts4; + break; + case 5: ext = bp->pps; break; } @@ -751,13 +931,27 @@ ptp_ocp_enable(struct ptp_clock_info *ptp_info, struct ptp_clock_request *rq, ext = bp->pps; break; case PTP_CLK_REQ_PEROUT: - if (on && - (rq->perout.period.sec != 1 || rq->perout.period.nsec != 0)) - return -EINVAL; - /* This is a request for 1PPS on an output SMA. - * Allow, but assume manual configuration. - */ - return 0; + switch (rq->perout.index) { + case 0: + /* This is a request for 1PPS on an output SMA. + * Allow, but assume manual configuration. + */ + if (on && (rq->perout.period.sec != 1 || + rq->perout.period.nsec != 0)) + return -EINVAL; + return 0; + case 1: + case 2: + case 3: + case 4: + req = rq->perout.index - 1; + ext = bp->signal_out[req]; + err = ptp_ocp_signal_from_perout(bp, req, &rq->perout); + if (err) + return err; + break; + } + break; default: return -EOPNOTSUPP; } @@ -769,6 +963,24 @@ ptp_ocp_enable(struct ptp_clock_info *ptp_info, struct ptp_clock_request *rq, return err; } +static int +ptp_ocp_verify(struct ptp_clock_info *ptp_info, unsigned pin, + enum ptp_pin_function func, unsigned chan) +{ + struct ptp_ocp *bp = container_of(ptp_info, struct ptp_ocp, ptp_info); + char buf[16]; + + if (func != PTP_PF_PEROUT) + return -EOPNOTSUPP; + + if (chan) + sprintf(buf, "OUT: GEN%d", chan); + else + sprintf(buf, "OUT: PHC"); + + return ptp_ocp_sma_store(bp, buf, pin + 1); +} + static const struct ptp_clock_info ptp_ocp_clock_info = { .owner = THIS_MODULE, .name = KBUILD_MODNAME, @@ -779,9 +991,10 @@ static const struct ptp_clock_info ptp_ocp_clock_info = { .adjfine = ptp_ocp_null_adjfine, .adjphase = ptp_ocp_null_adjphase, .enable = ptp_ocp_enable, + .verify = ptp_ocp_verify, .pps = true, - .n_ext_ts = 4, - .n_per_out = 1, + .n_ext_ts = 6, + .n_per_out = 5, }; static void @@ -1283,6 +1496,137 @@ ptp_ocp_register_i2c(struct ptp_ocp *bp, struct ocp_resource *r) return 0; } +/* The expectation is that this is triggered only on error. */ +static irqreturn_t +ptp_ocp_signal_irq(int irq, void *priv) +{ + struct ptp_ocp_ext_src *ext = priv; + struct signal_reg __iomem *reg = ext->mem; + struct ptp_ocp *bp = ext->bp; + u32 enable, status; + int gen; + + gen = ext->info->index - 1; + + enable = ioread32(®->enable); + status = ioread32(®->status); + + /* disable generator on error */ + if (status || !enable) { + iowrite32(0, ®->intr_mask); + iowrite32(0, ®->enable); + bp->signal[gen].running = false; + } + + iowrite32(0, ®->intr); /* ack interrupt */ + + return IRQ_HANDLED; +} + +static int +ptp_ocp_signal_set(struct ptp_ocp *bp, int gen, struct ptp_ocp_signal *s) +{ + struct ptp_system_timestamp sts; + struct timespec64 ts; + ktime_t start_ns; + int err; + + if (!s->period) + return 0; + + if (!s->pulse) + s->pulse = ktime_divns(s->period * s->duty, 100); + + err = ptp_ocp_gettimex(&bp->ptp_info, &ts, &sts); + if (err) + return err; + + start_ns = ktime_set(ts.tv_sec, ts.tv_nsec) + NSEC_PER_MSEC; + if (!s->start) { + /* roundup() does not work on 32-bit systems */ + s->start = DIV_ROUND_UP_ULL(start_ns, s->period); + s->start = ktime_add(s->start, s->phase); + } + + if (s->duty < 1 || s->duty > 99) + return -EINVAL; + + if (s->pulse < 1 || s->pulse > s->period) + return -EINVAL; + + if (s->start < start_ns) + return -EINVAL; + + bp->signal[gen] = *s; + + return 0; +} + +static int +ptp_ocp_signal_from_perout(struct ptp_ocp *bp, int gen, + struct ptp_perout_request *req) +{ + struct ptp_ocp_signal s = { }; + + s.polarity = bp->signal[gen].polarity; + s.period = ktime_set(req->period.sec, req->period.nsec); + if (!s.period) + return 0; + + if (req->flags & PTP_PEROUT_DUTY_CYCLE) { + s.pulse = ktime_set(req->on.sec, req->on.nsec); + s.duty = ktime_divns(s.pulse * 100, s.period); + } + + if (req->flags & PTP_PEROUT_PHASE) + s.phase = ktime_set(req->phase.sec, req->phase.nsec); + else + s.start = ktime_set(req->start.sec, req->start.nsec); + + return ptp_ocp_signal_set(bp, gen, &s); +} + +static int +ptp_ocp_signal_enable(void *priv, u32 req, bool enable) +{ + struct ptp_ocp_ext_src *ext = priv; + struct signal_reg __iomem *reg = ext->mem; + struct ptp_ocp *bp = ext->bp; + struct timespec64 ts; + int gen; + + gen = ext->info->index - 1; + + iowrite32(0, ®->intr_mask); + iowrite32(0, ®->enable); + bp->signal[gen].running = false; + if (!enable) + return 0; + + ts = ktime_to_timespec64(bp->signal[gen].start); + iowrite32(ts.tv_sec, ®->start_sec); + iowrite32(ts.tv_nsec, ®->start_ns); + + ts = ktime_to_timespec64(bp->signal[gen].period); + iowrite32(ts.tv_sec, ®->period_sec); + iowrite32(ts.tv_nsec, ®->period_ns); + + ts = ktime_to_timespec64(bp->signal[gen].pulse); + iowrite32(ts.tv_sec, ®->pulse_sec); + iowrite32(ts.tv_nsec, ®->pulse_ns); + + iowrite32(bp->signal[gen].polarity, ®->polarity); + iowrite32(0, ®->repeat_count); + + iowrite32(0, ®->intr); /* clear interrupt state */ + iowrite32(1, ®->intr_mask); /* enable interrupt */ + iowrite32(3, ®->enable); /* valid & enable */ + + bp->signal[gen].running = true; + + return 0; +} + static irqreturn_t ptp_ocp_ts_irq(int irq, void *priv) { @@ -1455,16 +1799,115 @@ ptp_ocp_nmea_out_init(struct ptp_ocp *bp) iowrite32(1, &bp->nmea_out->ctrl); /* enable */ } +static void +_ptp_ocp_signal_init(struct ptp_ocp_signal *s, struct signal_reg __iomem *reg) +{ + u32 val; + + iowrite32(0, ®->enable); /* disable */ + + val = ioread32(®->polarity); + s->polarity = val ? true : false; + s->duty = 50; +} + +static void +ptp_ocp_signal_init(struct ptp_ocp *bp) +{ + int i; + + for (i = 0; i < 4; i++) + if (bp->signal_out[i]) + _ptp_ocp_signal_init(&bp->signal[i], + bp->signal_out[i]->mem); +} + +static void +ptp_ocp_sma_init(struct ptp_ocp *bp) +{ + u32 reg; + int i; + + /* defaults */ + bp->sma[0].mode = SMA_MODE_IN; + bp->sma[1].mode = SMA_MODE_IN; + bp->sma[2].mode = SMA_MODE_OUT; + bp->sma[3].mode = SMA_MODE_OUT; + + /* If no SMA1 map, the pin functions and directions are fixed. */ + if (!bp->sma_map1) { + for (i = 0; i < 4; i++) { + bp->sma[i].fixed_fcn = true; + bp->sma[i].fixed_dir = true; + } + return; + } + + /* If SMA2 GPIO output map is all 1, it is not present. + * This indicates the firmware has fixed direction SMA pins. + */ + reg = ioread32(&bp->sma_map2->gpio2); + if (reg == 0xffffffff) { + for (i = 0; i < 4; i++) + bp->sma[i].fixed_dir = true; + } else { + reg = ioread32(&bp->sma_map1->gpio1); + bp->sma[0].mode = reg & BIT(15) ? SMA_MODE_IN : SMA_MODE_OUT; + bp->sma[1].mode = reg & BIT(31) ? SMA_MODE_IN : SMA_MODE_OUT; + + reg = ioread32(&bp->sma_map1->gpio2); + bp->sma[2].mode = reg & BIT(15) ? SMA_MODE_OUT : SMA_MODE_IN; + bp->sma[3].mode = reg & BIT(31) ? SMA_MODE_OUT : SMA_MODE_IN; + } +} + +static int +ptp_ocp_fb_set_pins(struct ptp_ocp *bp) +{ + struct ptp_pin_desc *config; + int i; + + config = kzalloc(sizeof(*config) * 4, GFP_KERNEL); + if (!config) + return -ENOMEM; + + for (i = 0; i < 4; i++) { + sprintf(config[i].name, "sma%d", i + 1); + config[i].index = i; + } + + bp->ptp_info.n_pins = 4; + bp->ptp_info.pin_config = config; + + return 0; +} + /* FB specific board initializers; last "resource" registered. */ static int ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r) { + int ver, err; + bp->flash_start = 1024 * 4096; bp->eeprom_map = fb_eeprom_map; bp->fw_version = ioread32(&bp->image->version); + bp->attr_tbl = fb_timecard_groups; + bp->fw_cap = OCP_CAP_BASIC; + + ver = bp->fw_version & 0xffff; + if (ver >= 19) + bp->fw_cap |= OCP_CAP_SIGNAL; + if (ver >= 20) + bp->fw_cap |= OCP_CAP_FREQ; ptp_ocp_tod_init(bp); ptp_ocp_nmea_out_init(bp); + ptp_ocp_sma_init(bp); + ptp_ocp_signal_init(bp); + + err = ptp_ocp_fb_set_pins(bp); + if (err) + return err; return ptp_ocp_init_clock(bp); } @@ -1566,38 +2009,8 @@ __handle_signal_inputs(struct ptp_ocp *bp, u32 val) * ANT4 == sma4 (out) */ -enum ptp_ocp_sma_mode { - SMA_MODE_IN, - SMA_MODE_OUT, -}; - -static struct ptp_ocp_sma_connector { - enum ptp_ocp_sma_mode mode; - bool fixed_mode; - u16 default_out_idx; -} ptp_ocp_sma_map[4] = { - { - .mode = SMA_MODE_IN, - .fixed_mode = true, - }, - { - .mode = SMA_MODE_IN, - .fixed_mode = true, - }, - { - .mode = SMA_MODE_OUT, - .fixed_mode = true, - .default_out_idx = 0, /* 10Mhz */ - }, - { - .mode = SMA_MODE_OUT, - .fixed_mode = true, - .default_out_idx = 1, /* PHC */ - }, -}; - static ssize_t -ptp_ocp_show_output(u32 val, char *buf, int default_idx) +ptp_ocp_show_output(u32 val, char *buf, int def_val) { const char *name; ssize_t count; @@ -1605,13 +2018,13 @@ ptp_ocp_show_output(u32 val, char *buf, int default_idx) count = sysfs_emit(buf, "OUT: "); name = ptp_ocp_select_name_from_val(ptp_ocp_sma_out, val); if (!name) - name = ptp_ocp_sma_out[default_idx].name; + name = ptp_ocp_select_name_from_val(ptp_ocp_sma_out, def_val); count += sysfs_emit_at(buf, count, "%s\n", name); return count; } static ssize_t -ptp_ocp_show_inputs(u32 val, char *buf, const char *zero_in) +ptp_ocp_show_inputs(u32 val, char *buf, int def_val) { const char *name; ssize_t count; @@ -1624,8 +2037,10 @@ ptp_ocp_show_inputs(u32 val, char *buf, const char *zero_in) count += sysfs_emit_at(buf, count, "%s ", name); } } - if (!val && zero_in) - count += sysfs_emit_at(buf, count, "%s ", zero_in); + if (!val && def_val >= 0) { + name = ptp_ocp_select_name_from_val(ptp_ocp_sma_in, def_val); + count += sysfs_emit_at(buf, count, "%s ", name); + } if (count) count--; count += sysfs_emit_at(buf, count, "\n"); @@ -1650,7 +2065,7 @@ sma_parse_inputs(const char *buf, enum ptp_ocp_sma_mode *mode) idx = 0; dir = *mode == SMA_MODE_IN ? 0 : 1; - if (!strcasecmp("IN:", argv[idx])) { + if (!strcasecmp("IN:", argv[0])) { dir = 0; idx++; } @@ -1671,102 +2086,126 @@ out: return ret; } +static u32 +ptp_ocp_sma_get(struct ptp_ocp *bp, int sma_nr, enum ptp_ocp_sma_mode mode) +{ + u32 __iomem *gpio; + u32 shift; + + if (bp->sma[sma_nr - 1].fixed_fcn) + return (sma_nr - 1) & 1; + + if (mode == SMA_MODE_IN) + gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1; + else + gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2; + shift = sma_nr & 1 ? 0 : 16; + + return (ioread32(gpio) >> shift) & 0xffff; +} + static ssize_t -ptp_ocp_sma_show(struct ptp_ocp *bp, int sma_nr, u32 val, char *buf, - const char *zero_in) +ptp_ocp_sma_show(struct ptp_ocp *bp, int sma_nr, char *buf, + int default_in_val, int default_out_val) { - struct ptp_ocp_sma_connector *sma = &ptp_ocp_sma_map[sma_nr - 1]; + struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1]; + u32 val; - if (sma->mode == SMA_MODE_IN) - return ptp_ocp_show_inputs(val, buf, zero_in); + val = ptp_ocp_sma_get(bp, sma_nr, sma->mode) & SMA_SELECT_MASK; - return ptp_ocp_show_output(val, buf, sma->default_out_idx); + if (sma->mode == SMA_MODE_IN) { + if (sma->disabled) + val = SMA_DISABLE; + return ptp_ocp_show_inputs(val, buf, default_in_val); + } + + return ptp_ocp_show_output(val, buf, default_out_val); } static ssize_t sma1_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ptp_ocp *bp = dev_get_drvdata(dev); - u32 val; - val = ioread32(&bp->sma->gpio1) & 0x3f; - return ptp_ocp_sma_show(bp, 1, val, buf, ptp_ocp_sma_in[0].name); + return ptp_ocp_sma_show(bp, 1, buf, 0, 1); } static ssize_t sma2_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ptp_ocp *bp = dev_get_drvdata(dev); - u32 val; - val = (ioread32(&bp->sma->gpio1) >> 16) & 0x3f; - return ptp_ocp_sma_show(bp, 2, val, buf, NULL); + return ptp_ocp_sma_show(bp, 2, buf, -1, 1); } static ssize_t sma3_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ptp_ocp *bp = dev_get_drvdata(dev); - u32 val; - val = ioread32(&bp->sma->gpio2) & 0x3f; - return ptp_ocp_sma_show(bp, 3, val, buf, NULL); + return ptp_ocp_sma_show(bp, 3, buf, -1, 0); } static ssize_t sma4_show(struct device *dev, struct device_attribute *attr, char *buf) { struct ptp_ocp *bp = dev_get_drvdata(dev); - u32 val; - val = (ioread32(&bp->sma->gpio2) >> 16) & 0x3f; - return ptp_ocp_sma_show(bp, 4, val, buf, NULL); + return ptp_ocp_sma_show(bp, 4, buf, -1, 1); } static void -ptp_ocp_sma_store_output(struct ptp_ocp *bp, u32 val, u32 shift) +ptp_ocp_sma_store_output(struct ptp_ocp *bp, int sma_nr, u32 val) { + u32 reg, mask, shift; unsigned long flags; - u32 gpio, mask; + u32 __iomem *gpio; + + gpio = sma_nr > 2 ? &bp->sma_map1->gpio2 : &bp->sma_map2->gpio2; + shift = sma_nr & 1 ? 0 : 16; mask = 0xffff << (16 - shift); spin_lock_irqsave(&bp->lock, flags); - gpio = ioread32(&bp->sma->gpio2); - gpio = (gpio & mask) | (val << shift); + reg = ioread32(gpio); + reg = (reg & mask) | (val << shift); - __handle_signal_outputs(bp, gpio); + __handle_signal_outputs(bp, reg); - iowrite32(gpio, &bp->sma->gpio2); + iowrite32(reg, gpio); spin_unlock_irqrestore(&bp->lock, flags); } static void -ptp_ocp_sma_store_inputs(struct ptp_ocp *bp, u32 val, u32 shift) +ptp_ocp_sma_store_inputs(struct ptp_ocp *bp, int sma_nr, u32 val) { + u32 reg, mask, shift; unsigned long flags; - u32 gpio, mask; + u32 __iomem *gpio; + + gpio = sma_nr > 2 ? &bp->sma_map2->gpio1 : &bp->sma_map1->gpio1; + shift = sma_nr & 1 ? 0 : 16; mask = 0xffff << (16 - shift); spin_lock_irqsave(&bp->lock, flags); - gpio = ioread32(&bp->sma->gpio1); - gpio = (gpio & mask) | (val << shift); + reg = ioread32(gpio); + reg = (reg & mask) | (val << shift); - __handle_signal_inputs(bp, gpio); + __handle_signal_inputs(bp, reg); - iowrite32(gpio, &bp->sma->gpio1); + iowrite32(reg, gpio); spin_unlock_irqrestore(&bp->lock, flags); } -static ssize_t -ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr, u32 shift) +static int +ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr) { - struct ptp_ocp_sma_connector *sma = &ptp_ocp_sma_map[sma_nr - 1]; + struct ptp_ocp_sma_connector *sma = &bp->sma[sma_nr - 1]; enum ptp_ocp_sma_mode mode; int val; @@ -1775,18 +2214,35 @@ ptp_ocp_sma_store(struct ptp_ocp *bp, const char *buf, int sma_nr, u32 shift) if (val < 0) return val; - if (mode != sma->mode && sma->fixed_mode) + if (sma->fixed_dir && (mode != sma->mode || val & SMA_DISABLE)) return -EOPNOTSUPP; + if (sma->fixed_fcn) { + if (val != ((sma_nr - 1) & 1)) + return -EOPNOTSUPP; + return 0; + } + + sma->disabled = !!(val & SMA_DISABLE); + if (mode != sma->mode) { - pr_err("Mode changes not supported yet.\n"); - return -EOPNOTSUPP; + if (mode == SMA_MODE_IN) + ptp_ocp_sma_store_output(bp, sma_nr, 0); + else + ptp_ocp_sma_store_inputs(bp, sma_nr, 0); + sma->mode = mode; } - if (sma->mode == SMA_MODE_IN) - ptp_ocp_sma_store_inputs(bp, val, shift); + if (!sma->fixed_dir) + val |= SMA_ENABLE; /* add enable bit */ + + if (sma->disabled) + val = 0; + + if (mode == SMA_MODE_IN) + ptp_ocp_sma_store_inputs(bp, sma_nr, val); else - ptp_ocp_sma_store_output(bp, val, shift); + ptp_ocp_sma_store_output(bp, sma_nr, val); return 0; } @@ -1798,7 +2254,7 @@ sma1_store(struct device *dev, struct device_attribute *attr, struct ptp_ocp *bp = dev_get_drvdata(dev); int err; - err = ptp_ocp_sma_store(bp, buf, 1, 0); + err = ptp_ocp_sma_store(bp, buf, 1); return err ? err : count; } @@ -1809,7 +2265,7 @@ sma2_store(struct device *dev, struct device_attribute *attr, struct ptp_ocp *bp = dev_get_drvdata(dev); int err; - err = ptp_ocp_sma_store(bp, buf, 2, 16); + err = ptp_ocp_sma_store(bp, buf, 2); return err ? err : count; } @@ -1820,7 +2276,7 @@ sma3_store(struct device *dev, struct device_attribute *attr, struct ptp_ocp *bp = dev_get_drvdata(dev); int err; - err = ptp_ocp_sma_store(bp, buf, 3, 0); + err = ptp_ocp_sma_store(bp, buf, 3); return err ? err : count; } @@ -1831,7 +2287,7 @@ sma4_store(struct device *dev, struct device_attribute *attr, struct ptp_ocp *bp = dev_get_drvdata(dev); int err; - err = ptp_ocp_sma_store(bp, buf, 4, 16); + err = ptp_ocp_sma_store(bp, buf, 4); return err ? err : count; } static DEVICE_ATTR_RW(sma1); @@ -1855,6 +2311,256 @@ available_sma_outputs_show(struct device *dev, } static DEVICE_ATTR_RO(available_sma_outputs); +#define EXT_ATTR_RO(_group, _name, _val) \ + struct dev_ext_attribute dev_attr_##_group##_val##_##_name = \ + { __ATTR_RO(_name), (void *)_val } +#define EXT_ATTR_RW(_group, _name, _val) \ + struct dev_ext_attribute dev_attr_##_group##_val##_##_name = \ + { __ATTR_RW(_name), (void *)_val } +#define to_ext_attr(x) container_of(x, struct dev_ext_attribute, attr) + +/* period [duty [phase [polarity]]] */ +static ssize_t +signal_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + struct ptp_ocp_signal s = { }; + int gen = (uintptr_t)ea->var; + int argc, err; + char **argv; + + argv = argv_split(GFP_KERNEL, buf, &argc); + if (!argv) + return -ENOMEM; + + err = -EINVAL; + s.duty = bp->signal[gen].duty; + s.phase = bp->signal[gen].phase; + s.period = bp->signal[gen].period; + s.polarity = bp->signal[gen].polarity; + + switch (argc) { + case 4: + argc--; + err = kstrtobool(argv[argc], &s.polarity); + if (err) + goto out; + fallthrough; + case 3: + argc--; + err = kstrtou64(argv[argc], 0, &s.phase); + if (err) + goto out; + fallthrough; + case 2: + argc--; + err = kstrtoint(argv[argc], 0, &s.duty); + if (err) + goto out; + fallthrough; + case 1: + argc--; + err = kstrtou64(argv[argc], 0, &s.period); + if (err) + goto out; + break; + default: + goto out; + } + + err = ptp_ocp_signal_set(bp, gen, &s); + if (err) + goto out; + + err = ptp_ocp_signal_enable(bp->signal_out[gen], gen, s.period != 0); + +out: + argv_free(argv); + return err ? err : count; +} + +static ssize_t +signal_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + struct ptp_ocp_signal *signal; + struct timespec64 ts; + ssize_t count; + int i; + + i = (uintptr_t)ea->var; + signal = &bp->signal[i]; + + count = sysfs_emit(buf, "%llu %d %llu %d", signal->period, + signal->duty, signal->phase, signal->polarity); + + ts = ktime_to_timespec64(signal->start); + count += sysfs_emit_at(buf, count, " %ptT TAI\n", &ts); + + return count; +} +static EXT_ATTR_RW(signal, signal, 0); +static EXT_ATTR_RW(signal, signal, 1); +static EXT_ATTR_RW(signal, signal, 2); +static EXT_ATTR_RW(signal, signal, 3); + +static ssize_t +duty_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%d\n", bp->signal[i].duty); +} +static EXT_ATTR_RO(signal, duty, 0); +static EXT_ATTR_RO(signal, duty, 1); +static EXT_ATTR_RO(signal, duty, 2); +static EXT_ATTR_RO(signal, duty, 3); + +static ssize_t +period_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%llu\n", bp->signal[i].period); +} +static EXT_ATTR_RO(signal, period, 0); +static EXT_ATTR_RO(signal, period, 1); +static EXT_ATTR_RO(signal, period, 2); +static EXT_ATTR_RO(signal, period, 3); + +static ssize_t +phase_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%llu\n", bp->signal[i].phase); +} +static EXT_ATTR_RO(signal, phase, 0); +static EXT_ATTR_RO(signal, phase, 1); +static EXT_ATTR_RO(signal, phase, 2); +static EXT_ATTR_RO(signal, phase, 3); + +static ssize_t +polarity_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%d\n", bp->signal[i].polarity); +} +static EXT_ATTR_RO(signal, polarity, 0); +static EXT_ATTR_RO(signal, polarity, 1); +static EXT_ATTR_RO(signal, polarity, 2); +static EXT_ATTR_RO(signal, polarity, 3); + +static ssize_t +running_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + + return sysfs_emit(buf, "%d\n", bp->signal[i].running); +} +static EXT_ATTR_RO(signal, running, 0); +static EXT_ATTR_RO(signal, running, 1); +static EXT_ATTR_RO(signal, running, 2); +static EXT_ATTR_RO(signal, running, 3); + +static ssize_t +start_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int i = (uintptr_t)ea->var; + struct timespec64 ts; + + ts = ktime_to_timespec64(bp->signal[i].start); + return sysfs_emit(buf, "%llu.%lu\n", ts.tv_sec, ts.tv_nsec); +} +static EXT_ATTR_RO(signal, start, 0); +static EXT_ATTR_RO(signal, start, 1); +static EXT_ATTR_RO(signal, start, 2); +static EXT_ATTR_RO(signal, start, 3); + +static ssize_t +seconds_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int idx = (uintptr_t)ea->var; + u32 val; + int err; + + err = kstrtou32(buf, 0, &val); + if (err) + return err; + if (val > 0xff) + return -EINVAL; + + if (val) + val = (val << 8) | 0x1; + + iowrite32(val, &bp->freq_in[idx]->ctrl); + + return count; +} + +static ssize_t +seconds_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int idx = (uintptr_t)ea->var; + u32 val; + + val = ioread32(&bp->freq_in[idx]->ctrl); + if (val & 1) + val = (val >> 8) & 0xff; + else + val = 0; + + return sysfs_emit(buf, "%u\n", val); +} +static EXT_ATTR_RW(freq, seconds, 0); +static EXT_ATTR_RW(freq, seconds, 1); +static EXT_ATTR_RW(freq, seconds, 2); +static EXT_ATTR_RW(freq, seconds, 3); + +static ssize_t +frequency_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct dev_ext_attribute *ea = to_ext_attr(attr); + struct ptp_ocp *bp = dev_get_drvdata(dev); + int idx = (uintptr_t)ea->var; + u32 val; + + val = ioread32(&bp->freq_in[idx]->status); + if (val & FREQ_STATUS_ERROR) + return sysfs_emit(buf, "error\n"); + if (val & FREQ_STATUS_OVERRUN) + return sysfs_emit(buf, "overrun\n"); + if (val & FREQ_STATUS_VALID) + return sysfs_emit(buf, "%lu\n", val & FREQ_STATUS_MASK); + return 0; +} +static EXT_ATTR_RO(freq, frequency, 0); +static EXT_ATTR_RO(freq, frequency, 1); +static EXT_ATTR_RO(freq, frequency, 2); +static EXT_ATTR_RO(freq, frequency, 3); + static ssize_t serialnum_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -2089,7 +2795,52 @@ tod_correction_store(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR_RW(tod_correction); -static struct attribute *timecard_attrs[] = { +#define _DEVICE_SIGNAL_GROUP_ATTRS(_nr) \ + static struct attribute *fb_timecard_signal##_nr##_attrs[] = { \ + &dev_attr_signal##_nr##_signal.attr.attr, \ + &dev_attr_signal##_nr##_duty.attr.attr, \ + &dev_attr_signal##_nr##_phase.attr.attr, \ + &dev_attr_signal##_nr##_period.attr.attr, \ + &dev_attr_signal##_nr##_polarity.attr.attr, \ + &dev_attr_signal##_nr##_running.attr.attr, \ + &dev_attr_signal##_nr##_start.attr.attr, \ + NULL, \ + } + +#define DEVICE_SIGNAL_GROUP(_name, _nr) \ + _DEVICE_SIGNAL_GROUP_ATTRS(_nr); \ + static const struct attribute_group \ + fb_timecard_signal##_nr##_group = { \ + .name = #_name, \ + .attrs = fb_timecard_signal##_nr##_attrs, \ +} + +DEVICE_SIGNAL_GROUP(gen1, 0); +DEVICE_SIGNAL_GROUP(gen2, 1); +DEVICE_SIGNAL_GROUP(gen3, 2); +DEVICE_SIGNAL_GROUP(gen4, 3); + +#define _DEVICE_FREQ_GROUP_ATTRS(_nr) \ + static struct attribute *fb_timecard_freq##_nr##_attrs[] = { \ + &dev_attr_freq##_nr##_seconds.attr.attr, \ + &dev_attr_freq##_nr##_frequency.attr.attr, \ + NULL, \ + } + +#define DEVICE_FREQ_GROUP(_name, _nr) \ + _DEVICE_FREQ_GROUP_ATTRS(_nr); \ + static const struct attribute_group \ + fb_timecard_freq##_nr##_group = { \ + .name = #_name, \ + .attrs = fb_timecard_freq##_nr##_attrs, \ +} + +DEVICE_FREQ_GROUP(freq1, 0); +DEVICE_FREQ_GROUP(freq2, 1); +DEVICE_FREQ_GROUP(freq3, 2); +DEVICE_FREQ_GROUP(freq4, 3); + +static struct attribute *fb_timecard_attrs[] = { &dev_attr_serialnum.attr, &dev_attr_gnss_sync.attr, &dev_attr_clock_source.attr, @@ -2108,33 +2859,111 @@ static struct attribute *timecard_attrs[] = { &dev_attr_tod_correction.attr, NULL, }; -ATTRIBUTE_GROUPS(timecard); +static const struct attribute_group fb_timecard_group = { + .attrs = fb_timecard_attrs, +}; +static const struct ocp_attr_group fb_timecard_groups[] = { + { .cap = OCP_CAP_BASIC, .group = &fb_timecard_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal0_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal1_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal2_group }, + { .cap = OCP_CAP_SIGNAL, .group = &fb_timecard_signal3_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq0_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq1_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq2_group }, + { .cap = OCP_CAP_FREQ, .group = &fb_timecard_freq3_group }, + { }, +}; -static const char * -gpio_map(u32 gpio, u32 bit, const char *pri, const char *sec, const char *def) +static void +gpio_input_map(char *buf, struct ptp_ocp *bp, u16 map[][2], u16 bit, + const char *def) { - const char *ans; + int i; - if (gpio & (1 << bit)) - ans = pri; - else if (gpio & (1 << (bit + 16))) - ans = sec; - else - ans = def; - return ans; + for (i = 0; i < 4; i++) { + if (bp->sma[i].mode != SMA_MODE_IN) + continue; + if (map[i][0] & (1 << bit)) { + sprintf(buf, "sma%d", i + 1); + return; + } + } + if (!def) + def = "----"; + strcpy(buf, def); } static void -gpio_multi_map(char *buf, u32 gpio, u32 bit, - const char *pri, const char *sec, const char *def) +gpio_output_map(char *buf, struct ptp_ocp *bp, u16 map[][2], u16 bit) { char *ans = buf; + int i; + + strcpy(ans, "----"); + for (i = 0; i < 4; i++) { + if (bp->sma[i].mode != SMA_MODE_OUT) + continue; + if (map[i][1] & (1 << bit)) + ans += sprintf(ans, "sma%d ", i + 1); + } +} + +static void +_signal_summary_show(struct seq_file *s, struct ptp_ocp *bp, int nr) +{ + struct signal_reg __iomem *reg = bp->signal_out[nr]->mem; + struct ptp_ocp_signal *signal = &bp->signal[nr]; + char label[8]; + bool on; + u32 val; + + if (!signal) + return; + + on = signal->running; + sprintf(label, "GEN%d", nr); + seq_printf(s, "%7s: %s, period:%llu duty:%d%% phase:%llu pol:%d", + label, on ? " ON" : "OFF", + signal->period, signal->duty, signal->phase, + signal->polarity); + + val = ioread32(®->enable); + seq_printf(s, " [%x", val); + val = ioread32(®->status); + seq_printf(s, " %x]", val); + + seq_printf(s, " start:%llu\n", signal->start); +} + +static void +_frequency_summary_show(struct seq_file *s, int nr, + struct frequency_reg __iomem *reg) +{ + char label[8]; + bool on; + u32 val; + + if (!reg) + return; - strcpy(ans, def); - if (gpio & (1 << bit)) - ans += sprintf(ans, "%s ", pri); - if (gpio & (1 << (bit + 16))) - ans += sprintf(ans, "%s ", sec); + sprintf(label, "FREQ%d", nr); + val = ioread32(®->ctrl); + on = val & 1; + val = (val >> 8) & 0xff; + seq_printf(s, "%7s: %s, sec:%u", + label, + on ? " ON" : "OFF", + val); + + val = ioread32(®->status); + if (val & FREQ_STATUS_ERROR) + seq_printf(s, ", error"); + if (val & FREQ_STATUS_OVERRUN) + seq_printf(s, ", overrun"); + if (val & FREQ_STATUS_VALID) + seq_printf(s, ", freq %lu Hz", val & FREQ_STATUS_MASK); + seq_printf(s, " reg:%x\n", val); } static int @@ -2142,21 +2971,19 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) { struct device *dev = s->private; struct ptp_system_timestamp sts; - u32 sma_in, sma_out, ctrl, val; + u16 sma_val[4][2], ctrl, val; struct ts_reg __iomem *ts_reg; struct timespec64 ts; struct ptp_ocp *bp; - const char *src; + char *src, *buf; bool on, map; - char *buf; + int i; buf = (char *)__get_free_page(GFP_KERNEL); if (!buf) return -ENOMEM; bp = dev_get_drvdata(dev); - sma_in = ioread32(&bp->sma->gpio1); - sma_out = ioread32(&bp->sma->gpio2); seq_printf(s, "%7s: /dev/ptp%d\n", "PTP", ptp_clock_index(bp->ptp)); if (bp->gnss_port != -1) @@ -2168,22 +2995,47 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) if (bp->nmea_port != -1) seq_printf(s, "%7s: /dev/ttyS%d\n", "NMEA", bp->nmea_port); + memset(sma_val, 0xff, sizeof(sma_val)); + if (bp->sma_map1) { + u32 reg; + + reg = ioread32(&bp->sma_map1->gpio1); + sma_val[0][0] = reg & 0xffff; + sma_val[1][0] = reg >> 16; + + reg = ioread32(&bp->sma_map1->gpio2); + sma_val[2][1] = reg & 0xffff; + sma_val[3][1] = reg >> 16; + + reg = ioread32(&bp->sma_map2->gpio1); + sma_val[2][0] = reg & 0xffff; + sma_val[3][0] = reg >> 16; + + reg = ioread32(&bp->sma_map2->gpio2); + sma_val[0][1] = reg & 0xffff; + sma_val[1][1] = reg >> 16; + } + sma1_show(dev, NULL, buf); - seq_printf(s, " sma1: %s", buf); + seq_printf(s, " sma1: %04x,%04x %s", + sma_val[0][0], sma_val[0][1], buf); sma2_show(dev, NULL, buf); - seq_printf(s, " sma2: %s", buf); + seq_printf(s, " sma2: %04x,%04x %s", + sma_val[1][0], sma_val[1][1], buf); sma3_show(dev, NULL, buf); - seq_printf(s, " sma3: %s", buf); + seq_printf(s, " sma3: %04x,%04x %s", + sma_val[2][0], sma_val[2][1], buf); sma4_show(dev, NULL, buf); - seq_printf(s, " sma4: %s", buf); + seq_printf(s, " sma4: %04x,%04x %s", + sma_val[3][0], sma_val[3][1], buf); if (bp->ts0) { ts_reg = bp->ts0->mem; on = ioread32(&ts_reg->enable); - src = "GNSS"; + src = "GNSS1"; seq_printf(s, "%7s: %s, src: %s\n", "TS0", on ? " ON" : "OFF", src); } @@ -2191,17 +3043,33 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) if (bp->ts1) { ts_reg = bp->ts1->mem; on = ioread32(&ts_reg->enable); - src = gpio_map(sma_in, 2, "sma1", "sma2", "----"); + gpio_input_map(buf, bp, sma_val, 2, NULL); seq_printf(s, "%7s: %s, src: %s\n", "TS1", - on ? " ON" : "OFF", src); + on ? " ON" : "OFF", buf); } if (bp->ts2) { ts_reg = bp->ts2->mem; on = ioread32(&ts_reg->enable); - src = gpio_map(sma_in, 3, "sma1", "sma2", "----"); + gpio_input_map(buf, bp, sma_val, 3, NULL); seq_printf(s, "%7s: %s, src: %s\n", "TS2", - on ? " ON" : "OFF", src); + on ? " ON" : "OFF", buf); + } + + if (bp->ts3) { + ts_reg = bp->ts3->mem; + on = ioread32(&ts_reg->enable); + gpio_input_map(buf, bp, sma_val, 6, NULL); + seq_printf(s, "%7s: %s, src: %s\n", "TS3", + on ? " ON" : "OFF", buf); + } + + if (bp->ts4) { + ts_reg = bp->ts4->mem; + on = ioread32(&ts_reg->enable); + gpio_input_map(buf, bp, sma_val, 7, NULL); + seq_printf(s, "%7s: %s, src: %s\n", "TS4", + on ? " ON" : "OFF", buf); } if (bp->pps) { @@ -2209,7 +3077,7 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) src = "PHC"; on = ioread32(&ts_reg->enable); map = !!(bp->pps_req_map & OCP_REQ_TIMESTAMP); - seq_printf(s, "%7s: %s, src: %s\n", "TS3", + seq_printf(s, "%7s: %s, src: %s\n", "TS5", on && map ? " ON" : "OFF", src); map = !!(bp->pps_req_map & OCP_REQ_PPS); @@ -2217,11 +3085,19 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) on && map ? " ON" : "OFF", src); } + if (bp->fw_cap & OCP_CAP_SIGNAL) + for (i = 0; i < 4; i++) + _signal_summary_show(s, bp, i); + + if (bp->fw_cap & OCP_CAP_FREQ) + for (i = 0; i < 4; i++) + _frequency_summary_show(s, i, bp->freq_in[i]); + if (bp->irig_out) { ctrl = ioread32(&bp->irig_out->ctrl); on = ctrl & IRIG_M_CTRL_ENABLE; val = ioread32(&bp->irig_out->status); - gpio_multi_map(buf, sma_out, 4, "sma3", "sma4", "----"); + gpio_output_map(buf, bp, sma_val, 4); seq_printf(s, "%7s: %s, error: %d, mode %d, out: %s\n", "IRIG", on ? " ON" : "OFF", val, (ctrl >> 16), buf); } @@ -2229,15 +3105,15 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) if (bp->irig_in) { on = ioread32(&bp->irig_in->ctrl) & IRIG_S_CTRL_ENABLE; val = ioread32(&bp->irig_in->status); - src = gpio_map(sma_in, 4, "sma1", "sma2", "----"); + gpio_input_map(buf, bp, sma_val, 4, NULL); seq_printf(s, "%7s: %s, error: %d, src: %s\n", "IRIG in", - on ? " ON" : "OFF", val, src); + on ? " ON" : "OFF", val, buf); } if (bp->dcf_out) { on = ioread32(&bp->dcf_out->ctrl) & DCF_M_CTRL_ENABLE; val = ioread32(&bp->dcf_out->status); - gpio_multi_map(buf, sma_out, 5, "sma3", "sma4", "----"); + gpio_output_map(buf, bp, sma_val, 5); seq_printf(s, "%7s: %s, error: %d, out: %s\n", "DCF", on ? " ON" : "OFF", val, buf); } @@ -2245,9 +3121,9 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) if (bp->dcf_in) { on = ioread32(&bp->dcf_in->ctrl) & DCF_S_CTRL_ENABLE; val = ioread32(&bp->dcf_in->status); - src = gpio_map(sma_in, 5, "sma1", "sma2", "----"); + gpio_input_map(buf, bp, sma_val, 5, NULL); seq_printf(s, "%7s: %s, error: %d, src: %s\n", "DCF in", - on ? " ON" : "OFF", val, src); + on ? " ON" : "OFF", val, buf); } if (bp->nmea_out) { @@ -2260,12 +3136,13 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) /* compute src for PPS1, used below. */ if (bp->pps_select) { val = ioread32(&bp->pps_select->gpio1); + src = &buf[80]; if (val & 0x01) - src = gpio_map(sma_in, 0, "sma1", "sma2", "----"); + gpio_input_map(src, bp, sma_val, 0, NULL); else if (val & 0x02) src = "MAC"; else if (val & 0x04) - src = "GNSS"; + src = "GNSS1"; else src = "----"; } else { @@ -2298,8 +3175,8 @@ ptp_ocp_summary_show(struct seq_file *s, void *data) /* reuses PPS1 src from earlier */ seq_printf(s, "MAC PPS1 src: %s\n", src); - src = gpio_map(sma_in, 1, "sma1", "sma2", "GNSS2"); - seq_printf(s, "MAC PPS2 src: %s\n", src); + gpio_input_map(buf, bp, sma_val, 1, "GNSS2"); + seq_printf(s, "MAC PPS2 src: %s\n", buf); if (!ptp_ocp_gettimex(&bp->ptp_info, &ts, &sts)) { struct timespec64 sys_ts; @@ -2497,6 +3374,7 @@ ptp_ocp_complete(struct ptp_ocp *bp) { struct pps_device *pps; char buf[32]; + int i, err; if (bp->gnss_port != -1) { sprintf(buf, "ttyS%d", bp->gnss_port); @@ -2521,8 +3399,13 @@ ptp_ocp_complete(struct ptp_ocp *bp) if (pps) ptp_ocp_symlink(bp, pps->dev, "pps"); - if (device_add_groups(&bp->dev, timecard_groups)) - pr_err("device add groups failed\n"); + for (i = 0; bp->attr_tbl[i].cap; i++) { + if (!(bp->attr_tbl[i].cap & bp->fw_cap)) + continue; + err = sysfs_create_group(&bp->dev.kobj, bp->attr_tbl[i].group); + if (err) + return err; + } ptp_ocp_debugfs_add_device(bp); @@ -2595,17 +3478,22 @@ static void ptp_ocp_detach_sysfs(struct ptp_ocp *bp) { struct device *dev = &bp->dev; + int i; sysfs_remove_link(&dev->kobj, "ttyGNSS"); sysfs_remove_link(&dev->kobj, "ttyMAC"); sysfs_remove_link(&dev->kobj, "ptp"); sysfs_remove_link(&dev->kobj, "pps"); - device_remove_groups(dev, timecard_groups); + if (bp->attr_tbl) + for (i = 0; bp->attr_tbl[i].cap; i++) + sysfs_remove_group(&dev->kobj, bp->attr_tbl[i].group); } static void ptp_ocp_detach(struct ptp_ocp *bp) { + int i; + ptp_ocp_debugfs_remove_device(bp); ptp_ocp_detach_sysfs(bp); if (timer_pending(&bp->watchdog)) @@ -2616,8 +3504,15 @@ ptp_ocp_detach(struct ptp_ocp *bp) ptp_ocp_unregister_ext(bp->ts1); if (bp->ts2) ptp_ocp_unregister_ext(bp->ts2); + if (bp->ts3) + ptp_ocp_unregister_ext(bp->ts3); + if (bp->ts4) + ptp_ocp_unregister_ext(bp->ts4); if (bp->pps) ptp_ocp_unregister_ext(bp->pps); + for (i = 0; i < 4; i++) + if (bp->signal_out[i]) + ptp_ocp_unregister_ext(bp->signal_out[i]); if (bp->gnss_port != -1) serial8250_unregister_port(bp->gnss_port); if (bp->gnss2_port != -1) @@ -2636,6 +3531,7 @@ ptp_ocp_detach(struct ptp_ocp *bp) pci_free_irq_vectors(bp->pdev); if (bp->ptp) ptp_clock_unregister(bp->ptp); + kfree(bp->ptp_info.pin_config); device_unregister(&bp->dev); } @@ -2668,7 +3564,7 @@ ptp_ocp_probe(struct pci_dev *pdev, const struct pci_device_id *id) * allow this - if not all of the IRQ's are returned, skip the * extra devices and just register the clock. */ - err = pci_alloc_irq_vectors(pdev, 1, 11, PCI_IRQ_MSI | PCI_IRQ_MSIX); + err = pci_alloc_irq_vectors(pdev, 1, 17, PCI_IRQ_MSI | PCI_IRQ_MSIX); if (err < 0) { dev_err(&pdev->dev, "alloc_irq_vectors err: %d\n", err); goto out; |