spi: make `cs_change_delay` the first user of the `spi_delay` logic
[linux-2.6-microblaze.git] / drivers / spi / spi.c
index f9502db..b69c140 100644 (file)
@@ -1106,42 +1106,78 @@ static void _spi_transfer_delay_ns(u32 ns)
        }
 }
 
-static void _spi_transfer_cs_change_delay(struct spi_message *msg,
-                                         struct spi_transfer *xfer)
+static int _spi_delay_to_ns(struct spi_delay *_delay, struct spi_transfer *xfer)
 {
-       u32 delay = xfer->cs_change_delay;
-       u32 unit = xfer->cs_change_delay_unit;
+       u32 delay = _delay->value;
+       u32 unit = _delay->unit;
        u32 hz;
 
-       /* return early on "fast" mode - for everything but USECS */
-       if (!delay && unit != SPI_DELAY_UNIT_USECS)
-               return;
+       if (!delay)
+               return 0;
 
        switch (unit) {
        case SPI_DELAY_UNIT_USECS:
-               /* for compatibility use default of 10us */
-               if (!delay)
-                       delay = 10000;
-               else
-                       delay *= 1000;
+               delay *= 1000;
                break;
        case SPI_DELAY_UNIT_NSECS: /* nothing to do here */
                break;
        case SPI_DELAY_UNIT_SCK:
+               /* clock cycles need to be obtained from spi_transfer */
+               if (!xfer)
+                       return -EINVAL;
                /* if there is no effective speed know, then approximate
                 * by underestimating with half the requested hz
                 */
                hz = xfer->effective_speed_hz ?: xfer->speed_hz / 2;
+               if (!hz)
+                       return -EINVAL;
                delay *= DIV_ROUND_UP(1000000000, hz);
                break;
        default:
+               return -EINVAL;
+       }
+
+       return delay;
+}
+
+int spi_delay_exec(struct spi_delay *_delay, struct spi_transfer *xfer)
+{
+       int delay;
+
+       if (!_delay)
+               return -EINVAL;
+
+       delay = _spi_delay_to_ns(_delay, xfer);
+       if (delay < 0)
+               return delay;
+
+       _spi_transfer_delay_ns(delay);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(spi_delay_exec);
+
+static void _spi_transfer_cs_change_delay(struct spi_message *msg,
+                                         struct spi_transfer *xfer)
+{
+       u32 delay = xfer->cs_change_delay.value;
+       u32 unit = xfer->cs_change_delay.unit;
+       int ret;
+
+       /* return early on "fast" mode - for everything but USECS */
+       if (!delay) {
+               if (unit == SPI_DELAY_UNIT_USECS)
+                       _spi_transfer_delay_ns(10000);
+               return;
+       }
+
+       ret = spi_delay_exec(&xfer->cs_change_delay, xfer);
+       if (ret) {
                dev_err_once(&msg->spi->dev,
                             "Use of unsupported delay unit %i, using default of 10us\n",
-                            xfer->cs_change_delay_unit);
-               delay = 10000;
+                            unit);
+               _spi_transfer_delay_ns(10000);
        }
-       /* now sleep for the requested amount of time */
-       _spi_transfer_delay_ns(delay);
 }
 
 /*
@@ -1171,6 +1207,11 @@ static int spi_transfer_one_message(struct spi_controller *ctlr,
                spi_statistics_add_transfer_stats(statm, xfer, ctlr);
                spi_statistics_add_transfer_stats(stats, xfer, ctlr);
 
+               if (!ctlr->ptp_sts_supported) {
+                       xfer->ptp_sts_word_pre = 0;
+                       ptp_read_system_prets(xfer->ptp_sts);
+               }
+
                if (xfer->tx_buf || xfer->rx_buf) {
                        reinit_completion(&ctlr->xfer_completion);
 
@@ -1197,6 +1238,11 @@ static int spi_transfer_one_message(struct spi_controller *ctlr,
                                        xfer->len);
                }
 
+               if (!ctlr->ptp_sts_supported) {
+                       ptp_read_system_postts(xfer->ptp_sts);
+                       xfer->ptp_sts_word_post = xfer->len;
+               }
+
                trace_spi_transfer_stop(msg, xfer);
 
                if (msg->status != -EINPROGRESS)
@@ -1265,6 +1311,7 @@ EXPORT_SYMBOL_GPL(spi_finalize_current_transfer);
  */
 static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
 {
+       struct spi_transfer *xfer;
        struct spi_message *msg;
        bool was_busy = false;
        unsigned long flags;
@@ -1391,6 +1438,13 @@ static void __spi_pump_messages(struct spi_controller *ctlr, bool in_kthread)
                goto out;
        }
 
+       if (!ctlr->ptp_sts_supported && !ctlr->transfer_one) {
+               list_for_each_entry(xfer, &msg->transfers, transfer_list) {
+                       xfer->ptp_sts_word_pre = 0;
+                       ptp_read_system_prets(xfer->ptp_sts);
+               }
+       }
+
        ret = ctlr->transfer_one_message(ctlr, msg);
        if (ret) {
                dev_err(&ctlr->dev,
@@ -1418,6 +1472,99 @@ static void spi_pump_messages(struct kthread_work *work)
        __spi_pump_messages(ctlr, true);
 }
 
+/**
+ * spi_take_timestamp_pre - helper for drivers to collect the beginning of the
+ *                         TX timestamp for the requested byte from the SPI
+ *                         transfer. The frequency with which this function
+ *                         must be called (once per word, once for the whole
+ *                         transfer, once per batch of words etc) is arbitrary
+ *                         as long as the @tx buffer offset is greater than or
+ *                         equal to the requested byte at the time of the
+ *                         call. The timestamp is only taken once, at the
+ *                         first such call. It is assumed that the driver
+ *                         advances its @tx buffer pointer monotonically.
+ * @ctlr: Pointer to the spi_controller structure of the driver
+ * @xfer: Pointer to the transfer being timestamped
+ * @tx: Pointer to the current word within the xfer->tx_buf that the driver is
+ *     preparing to transmit right now.
+ * @irqs_off: If true, will disable IRQs and preemption for the duration of the
+ *           transfer, for less jitter in time measurement. Only compatible
+ *           with PIO drivers. If true, must follow up with
+ *           spi_take_timestamp_post or otherwise system will crash.
+ *           WARNING: for fully predictable results, the CPU frequency must
+ *           also be under control (governor).
+ */
+void spi_take_timestamp_pre(struct spi_controller *ctlr,
+                           struct spi_transfer *xfer,
+                           const void *tx, bool irqs_off)
+{
+       u8 bytes_per_word = DIV_ROUND_UP(xfer->bits_per_word, 8);
+
+       if (!xfer->ptp_sts)
+               return;
+
+       if (xfer->timestamped_pre)
+               return;
+
+       if (tx < (xfer->tx_buf + xfer->ptp_sts_word_pre * bytes_per_word))
+               return;
+
+       /* Capture the resolution of the timestamp */
+       xfer->ptp_sts_word_pre = (tx - xfer->tx_buf) / bytes_per_word;
+
+       xfer->timestamped_pre = true;
+
+       if (irqs_off) {
+               local_irq_save(ctlr->irq_flags);
+               preempt_disable();
+       }
+
+       ptp_read_system_prets(xfer->ptp_sts);
+}
+EXPORT_SYMBOL_GPL(spi_take_timestamp_pre);
+
+/**
+ * spi_take_timestamp_post - helper for drivers to collect the end of the
+ *                          TX timestamp for the requested byte from the SPI
+ *                          transfer. Can be called with an arbitrary
+ *                          frequency: only the first call where @tx exceeds
+ *                          or is equal to the requested word will be
+ *                          timestamped.
+ * @ctlr: Pointer to the spi_controller structure of the driver
+ * @xfer: Pointer to the transfer being timestamped
+ * @tx: Pointer to the current word within the xfer->tx_buf that the driver has
+ *     just transmitted.
+ * @irqs_off: If true, will re-enable IRQs and preemption for the local CPU.
+ */
+void spi_take_timestamp_post(struct spi_controller *ctlr,
+                            struct spi_transfer *xfer,
+                            const void *tx, bool irqs_off)
+{
+       u8 bytes_per_word = DIV_ROUND_UP(xfer->bits_per_word, 8);
+
+       if (!xfer->ptp_sts)
+               return;
+
+       if (xfer->timestamped_post)
+               return;
+
+       if (tx < (xfer->tx_buf + xfer->ptp_sts_word_post * bytes_per_word))
+               return;
+
+       ptp_read_system_postts(xfer->ptp_sts);
+
+       if (irqs_off) {
+               local_irq_restore(ctlr->irq_flags);
+               preempt_enable();
+       }
+
+       /* Capture the resolution of the timestamp */
+       xfer->ptp_sts_word_post = (tx - xfer->tx_buf) / bytes_per_word;
+
+       xfer->timestamped_post = true;
+}
+EXPORT_SYMBOL_GPL(spi_take_timestamp_post);
+
 /**
  * spi_set_thread_rt - set the controller to pump at realtime priority
  * @ctlr: controller to boost priority of
@@ -1503,6 +1650,7 @@ EXPORT_SYMBOL_GPL(spi_get_next_queued_message);
  */
 void spi_finalize_current_message(struct spi_controller *ctlr)
 {
+       struct spi_transfer *xfer;
        struct spi_message *mesg;
        unsigned long flags;
        int ret;
@@ -1511,6 +1659,13 @@ void spi_finalize_current_message(struct spi_controller *ctlr)
        mesg = ctlr->cur_msg;
        spin_unlock_irqrestore(&ctlr->queue_lock, flags);
 
+       if (!ctlr->ptp_sts_supported && !ctlr->transfer_one) {
+               list_for_each_entry(xfer, &mesg->transfers, transfer_list) {
+                       ptp_read_system_postts(xfer->ptp_sts);
+                       xfer->ptp_sts_word_post = xfer->len;
+               }
+       }
+
        spi_unmap_msg(ctlr, mesg);
 
        if (ctlr->cur_msg_prepared && ctlr->unprepare_message) {
@@ -3273,6 +3428,7 @@ static int __spi_validate(struct spi_device *spi, struct spi_message *message)
 static int __spi_async(struct spi_device *spi, struct spi_message *message)
 {
        struct spi_controller *ctlr = spi->controller;
+       struct spi_transfer *xfer;
 
        /*
         * Some controllers do not support doing regular SPI transfers. Return
@@ -3288,6 +3444,13 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message)
 
        trace_spi_message_submit(message);
 
+       if (!ctlr->ptp_sts_supported) {
+               list_for_each_entry(xfer, &message->transfers, transfer_list) {
+                       xfer->ptp_sts_word_pre = 0;
+                       ptp_read_system_prets(xfer->ptp_sts);
+               }
+       }
+
        return ctlr->transfer(spi, message);
 }