| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * SPI bus driver for the Virtio SPI controller |
| * Copyright (C) 2023 OpenSynergy GmbH |
| * Copyright (C) 2025 Qualcomm Innovation Center, Inc. All rights reserved. |
| */ |
| |
| #include <linux/completion.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/module.h> |
| #include <linux/spi/spi.h> |
| #include <linux/stddef.h> |
| #include <linux/virtio.h> |
| #include <linux/virtio_ring.h> |
| #include <linux/virtio_spi.h> |
| |
| #define VIRTIO_SPI_MODE_MASK \ |
| (SPI_MODE_X_MASK | SPI_CS_HIGH | SPI_LSB_FIRST) |
| |
| struct virtio_spi_req { |
| struct completion completion; |
| const u8 *tx_buf; |
| u8 *rx_buf; |
| struct spi_transfer_head transfer_head ____cacheline_aligned; |
| struct spi_transfer_result result; |
| }; |
| |
| struct virtio_spi_priv { |
| /* The virtio device we're associated with */ |
| struct virtio_device *vdev; |
| /* Pointer to the virtqueue */ |
| struct virtqueue *vq; |
| /* Copy of config space mode_func_supported */ |
| u32 mode_func_supported; |
| /* Copy of config space max_freq_hz */ |
| u32 max_freq_hz; |
| }; |
| |
| static void virtio_spi_msg_done(struct virtqueue *vq) |
| { |
| struct virtio_spi_req *req; |
| unsigned int len; |
| |
| while ((req = virtqueue_get_buf(vq, &len))) |
| complete(&req->completion); |
| } |
| |
| /* |
| * virtio_spi_set_delays - Set delay parameters for SPI transfer |
| * |
| * This function sets various delay parameters for SPI transfer, |
| * including delay after CS asserted, timing intervals between |
| * adjacent words within a transfer, delay before and after CS |
| * deasserted. It converts these delay parameters to nanoseconds |
| * using spi_delay_to_ns and stores the results in spi_transfer_head |
| * structure. |
| * If the conversion fails, the function logs a warning message and |
| * returns an error code. |
| * . . . . . . . . . . |
| * Delay + A + + B + + C + D + E + F + A + |
| * . . . . . . . . . . |
| * ___. . . . . . .___.___. . |
| * CS# |___.______.____.____.___.___| . |___._____________ |
| * . . . . . . . . . . |
| * . . . . . . . . . . |
| * SCLK__.___.___NNN_____NNN__.___.___.___.___.___.___NNN_______ |
| * |
| * NOTE: 1st transfer has two words, the delay between these two words are |
| * 'B' in the diagram. |
| * |
| * A => struct spi_device -> cs_setup |
| * B => max{struct spi_transfer -> word_delay, struct spi_device -> word_delay} |
| * Note: spi_device and spi_transfer both have word_delay, Linux |
| * choose the bigger one, refer to _spi_xfer_word_delay_update function |
| * C => struct spi_transfer -> delay |
| * D => struct spi_device -> cs_hold |
| * E => struct spi_device -> cs_inactive |
| * F => struct spi_transfer -> cs_change_delay |
| * |
| * So the corresponding relationship: |
| * A <===> cs_setup_ns (after CS asserted) |
| * B <===> word_delay_ns (delay between adjacent words within a transfer) |
| * C+D <===> cs_delay_hold_ns (before CS deasserted) |
| * E+F <===> cs_change_delay_inactive_ns (after CS deasserted, these two |
| * values are also recommended in the Linux driver to be added up) |
| */ |
| static int virtio_spi_set_delays(struct spi_transfer_head *th, |
| struct spi_device *spi, |
| struct spi_transfer *xfer) |
| { |
| int cs_setup; |
| int cs_word_delay_xfer; |
| int cs_word_delay_spi; |
| int delay; |
| int cs_hold; |
| int cs_inactive; |
| int cs_change_delay; |
| |
| cs_setup = spi_delay_to_ns(&spi->cs_setup, xfer); |
| if (cs_setup < 0) { |
| dev_warn(&spi->dev, "Cannot convert cs_setup\n"); |
| return cs_setup; |
| } |
| th->cs_setup_ns = cpu_to_le32(cs_setup); |
| |
| cs_word_delay_xfer = spi_delay_to_ns(&xfer->word_delay, xfer); |
| if (cs_word_delay_xfer < 0) { |
| dev_warn(&spi->dev, "Cannot convert cs_word_delay_xfer\n"); |
| return cs_word_delay_xfer; |
| } |
| cs_word_delay_spi = spi_delay_to_ns(&spi->word_delay, xfer); |
| if (cs_word_delay_spi < 0) { |
| dev_warn(&spi->dev, "Cannot convert cs_word_delay_spi\n"); |
| return cs_word_delay_spi; |
| } |
| |
| th->word_delay_ns = cpu_to_le32(max(cs_word_delay_spi, cs_word_delay_xfer)); |
| |
| delay = spi_delay_to_ns(&xfer->delay, xfer); |
| if (delay < 0) { |
| dev_warn(&spi->dev, "Cannot convert delay\n"); |
| return delay; |
| } |
| cs_hold = spi_delay_to_ns(&spi->cs_hold, xfer); |
| if (cs_hold < 0) { |
| dev_warn(&spi->dev, "Cannot convert cs_hold\n"); |
| return cs_hold; |
| } |
| th->cs_delay_hold_ns = cpu_to_le32(delay + cs_hold); |
| |
| cs_inactive = spi_delay_to_ns(&spi->cs_inactive, xfer); |
| if (cs_inactive < 0) { |
| dev_warn(&spi->dev, "Cannot convert cs_inactive\n"); |
| return cs_inactive; |
| } |
| cs_change_delay = spi_delay_to_ns(&xfer->cs_change_delay, xfer); |
| if (cs_change_delay < 0) { |
| dev_warn(&spi->dev, "Cannot convert cs_change_delay\n"); |
| return cs_change_delay; |
| } |
| th->cs_change_delay_inactive_ns = |
| cpu_to_le32(cs_inactive + cs_change_delay); |
| |
| return 0; |
| } |
| |
| static int virtio_spi_transfer_one(struct spi_controller *ctrl, |
| struct spi_device *spi, |
| struct spi_transfer *xfer) |
| { |
| struct virtio_spi_priv *priv = spi_controller_get_devdata(ctrl); |
| struct virtio_spi_req *spi_req __free(kfree) = NULL; |
| struct spi_transfer_head *th; |
| struct scatterlist sg_out_head, sg_out_payload; |
| struct scatterlist sg_in_result, sg_in_payload; |
| struct scatterlist *sgs[4]; |
| unsigned int outcnt = 0; |
| unsigned int incnt = 0; |
| int ret; |
| |
| spi_req = kzalloc(sizeof(*spi_req), GFP_KERNEL); |
| if (!spi_req) |
| return -ENOMEM; |
| |
| init_completion(&spi_req->completion); |
| |
| th = &spi_req->transfer_head; |
| |
| /* Fill struct spi_transfer_head */ |
| th->chip_select_id = spi_get_chipselect(spi, 0); |
| th->bits_per_word = spi->bits_per_word; |
| th->cs_change = xfer->cs_change; |
| th->tx_nbits = xfer->tx_nbits; |
| th->rx_nbits = xfer->rx_nbits; |
| th->reserved[0] = 0; |
| th->reserved[1] = 0; |
| th->reserved[2] = 0; |
| |
| static_assert(VIRTIO_SPI_CPHA == SPI_CPHA, |
| "VIRTIO_SPI_CPHA must match SPI_CPHA"); |
| static_assert(VIRTIO_SPI_CPOL == SPI_CPOL, |
| "VIRTIO_SPI_CPOL must match SPI_CPOL"); |
| static_assert(VIRTIO_SPI_CS_HIGH == SPI_CS_HIGH, |
| "VIRTIO_SPI_CS_HIGH must match SPI_CS_HIGH"); |
| static_assert(VIRTIO_SPI_MODE_LSB_FIRST == SPI_LSB_FIRST, |
| "VIRTIO_SPI_MODE_LSB_FIRST must match SPI_LSB_FIRST"); |
| |
| th->mode = cpu_to_le32(spi->mode & VIRTIO_SPI_MODE_MASK); |
| if (spi->mode & SPI_LOOP) |
| th->mode |= cpu_to_le32(VIRTIO_SPI_MODE_LOOP); |
| |
| th->freq = cpu_to_le32(xfer->speed_hz); |
| |
| ret = virtio_spi_set_delays(th, spi, xfer); |
| if (ret) |
| goto msg_done; |
| |
| /* Set buffers */ |
| spi_req->tx_buf = xfer->tx_buf; |
| spi_req->rx_buf = xfer->rx_buf; |
| |
| /* Prepare sending of virtio message */ |
| init_completion(&spi_req->completion); |
| |
| sg_init_one(&sg_out_head, th, sizeof(*th)); |
| sgs[outcnt] = &sg_out_head; |
| outcnt++; |
| |
| if (spi_req->tx_buf) { |
| sg_init_one(&sg_out_payload, spi_req->tx_buf, xfer->len); |
| sgs[outcnt] = &sg_out_payload; |
| outcnt++; |
| } |
| |
| if (spi_req->rx_buf) { |
| sg_init_one(&sg_in_payload, spi_req->rx_buf, xfer->len); |
| sgs[outcnt] = &sg_in_payload; |
| incnt++; |
| } |
| |
| sg_init_one(&sg_in_result, &spi_req->result, |
| sizeof(struct spi_transfer_result)); |
| sgs[outcnt + incnt] = &sg_in_result; |
| incnt++; |
| |
| ret = virtqueue_add_sgs(priv->vq, sgs, outcnt, incnt, spi_req, |
| GFP_KERNEL); |
| if (ret) |
| goto msg_done; |
| |
| /* Simple implementation: There can be only one transfer in flight */ |
| virtqueue_kick(priv->vq); |
| |
| wait_for_completion(&spi_req->completion); |
| |
| /* Read result from message and translate return code */ |
| switch (spi_req->result.result) { |
| case VIRTIO_SPI_TRANS_OK: |
| break; |
| case VIRTIO_SPI_PARAM_ERR: |
| ret = -EINVAL; |
| break; |
| case VIRTIO_SPI_TRANS_ERR: |
| ret = -EIO; |
| break; |
| default: |
| ret = -EIO; |
| break; |
| } |
| |
| msg_done: |
| if (ret) |
| ctrl->cur_msg->status = ret; |
| |
| return ret; |
| } |
| |
| static void virtio_spi_read_config(struct virtio_device *vdev) |
| { |
| struct spi_controller *ctrl = dev_get_drvdata(&vdev->dev); |
| struct virtio_spi_priv *priv = vdev->priv; |
| u8 cs_max_number; |
| u8 tx_nbits_supported; |
| u8 rx_nbits_supported; |
| |
| cs_max_number = virtio_cread8(vdev, offsetof(struct virtio_spi_config, |
| cs_max_number)); |
| ctrl->num_chipselect = cs_max_number; |
| |
| /* Set the mode bits which are understood by this driver */ |
| priv->mode_func_supported = |
| virtio_cread32(vdev, offsetof(struct virtio_spi_config, |
| mode_func_supported)); |
| ctrl->mode_bits = priv->mode_func_supported & |
| (VIRTIO_SPI_CS_HIGH | VIRTIO_SPI_MODE_LSB_FIRST); |
| if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPHA_1) |
| ctrl->mode_bits |= VIRTIO_SPI_CPHA; |
| if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_CPOL_1) |
| ctrl->mode_bits |= VIRTIO_SPI_CPOL; |
| if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LSB_FIRST) |
| ctrl->mode_bits |= SPI_LSB_FIRST; |
| if (priv->mode_func_supported & VIRTIO_SPI_MF_SUPPORT_LOOPBACK) |
| ctrl->mode_bits |= SPI_LOOP; |
| tx_nbits_supported = |
| virtio_cread8(vdev, offsetof(struct virtio_spi_config, |
| tx_nbits_supported)); |
| if (tx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_DUAL) |
| ctrl->mode_bits |= SPI_TX_DUAL; |
| if (tx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_QUAD) |
| ctrl->mode_bits |= SPI_TX_QUAD; |
| if (tx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_OCTAL) |
| ctrl->mode_bits |= SPI_TX_OCTAL; |
| rx_nbits_supported = |
| virtio_cread8(vdev, offsetof(struct virtio_spi_config, |
| rx_nbits_supported)); |
| if (rx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_DUAL) |
| ctrl->mode_bits |= SPI_RX_DUAL; |
| if (rx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_QUAD) |
| ctrl->mode_bits |= SPI_RX_QUAD; |
| if (rx_nbits_supported & VIRTIO_SPI_RX_TX_SUPPORT_OCTAL) |
| ctrl->mode_bits |= SPI_RX_OCTAL; |
| |
| ctrl->bits_per_word_mask = |
| virtio_cread32(vdev, offsetof(struct virtio_spi_config, |
| bits_per_word_mask)); |
| |
| priv->max_freq_hz = |
| virtio_cread32(vdev, offsetof(struct virtio_spi_config, |
| max_freq_hz)); |
| } |
| |
| static int virtio_spi_find_vqs(struct virtio_spi_priv *priv) |
| { |
| struct virtqueue *vq; |
| |
| vq = virtio_find_single_vq(priv->vdev, virtio_spi_msg_done, "spi-rq"); |
| if (IS_ERR(vq)) |
| return PTR_ERR(vq); |
| priv->vq = vq; |
| return 0; |
| } |
| |
| /* Function must not be called before virtio_spi_find_vqs() has been run */ |
| static void virtio_spi_del_vq(void *data) |
| { |
| struct virtio_device *vdev = data; |
| |
| virtio_reset_device(vdev); |
| vdev->config->del_vqs(vdev); |
| } |
| |
| static int virtio_spi_probe(struct virtio_device *vdev) |
| { |
| struct virtio_spi_priv *priv; |
| struct spi_controller *ctrl; |
| int ret; |
| |
| ctrl = devm_spi_alloc_host(&vdev->dev, sizeof(*priv)); |
| if (!ctrl) |
| return -ENOMEM; |
| |
| priv = spi_controller_get_devdata(ctrl); |
| priv->vdev = vdev; |
| vdev->priv = priv; |
| |
| device_set_node(&ctrl->dev, dev_fwnode(&vdev->dev)); |
| |
| dev_set_drvdata(&vdev->dev, ctrl); |
| |
| virtio_spi_read_config(vdev); |
| |
| ctrl->transfer_one = virtio_spi_transfer_one; |
| |
| ret = virtio_spi_find_vqs(priv); |
| if (ret) |
| return dev_err_probe(&vdev->dev, ret, "Cannot setup virtqueues\n"); |
| |
| /* Register cleanup for virtqueues using devm */ |
| ret = devm_add_action_or_reset(&vdev->dev, virtio_spi_del_vq, vdev); |
| if (ret) |
| return dev_err_probe(&vdev->dev, ret, "Cannot register virtqueue cleanup\n"); |
| |
| /* Use devm version to register controller */ |
| ret = devm_spi_register_controller(&vdev->dev, ctrl); |
| if (ret) |
| return dev_err_probe(&vdev->dev, ret, "Cannot register controller\n"); |
| |
| return 0; |
| } |
| |
| static int virtio_spi_freeze(struct device *dev) |
| { |
| struct spi_controller *ctrl = dev_get_drvdata(dev); |
| struct virtio_device *vdev = dev_to_virtio(dev); |
| int ret; |
| |
| ret = spi_controller_suspend(ctrl); |
| if (ret) { |
| dev_warn(dev, "cannot suspend controller (%d)\n", ret); |
| return ret; |
| } |
| |
| virtio_spi_del_vq(vdev); |
| return 0; |
| } |
| |
| static int virtio_spi_restore(struct device *dev) |
| { |
| struct spi_controller *ctrl = dev_get_drvdata(dev); |
| struct virtio_device *vdev = dev_to_virtio(dev); |
| int ret; |
| |
| ret = virtio_spi_find_vqs(vdev->priv); |
| if (ret) { |
| dev_err(dev, "problem starting vqueue (%d)\n", ret); |
| return ret; |
| } |
| |
| ret = spi_controller_resume(ctrl); |
| if (ret) |
| dev_err(dev, "problem resuming controller (%d)\n", ret); |
| |
| return ret; |
| } |
| |
| static struct virtio_device_id virtio_spi_id_table[] = { |
| { VIRTIO_ID_SPI, VIRTIO_DEV_ANY_ID }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(virtio, virtio_spi_id_table); |
| |
| static const struct dev_pm_ops virtio_spi_pm_ops = { |
| .freeze = pm_sleep_ptr(virtio_spi_freeze), |
| .restore = pm_sleep_ptr(virtio_spi_restore), |
| }; |
| |
| static struct virtio_driver virtio_spi_driver = { |
| .driver = { |
| .name = KBUILD_MODNAME, |
| .pm = &virtio_spi_pm_ops, |
| }, |
| .id_table = virtio_spi_id_table, |
| .probe = virtio_spi_probe, |
| }; |
| module_virtio_driver(virtio_spi_driver); |
| |
| MODULE_AUTHOR("OpenSynergy GmbH"); |
| MODULE_AUTHOR("Haixu Cui <quic_haixcui@quicinc.com>"); |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("Virtio SPI bus driver"); |