From 503efdfd8c5112d968c962d90712d5589f23fa15 Mon Sep 17 00:00:00 2001 From: Haixu Cui Date: Thu, 9 Mar 2023 15:41:16 +0800 Subject: [PATCH] Virtio-spi: Add snapshot of virtio SPI frontend driver Virtio SPI frontend driver snapshot from msm-5.15 commit 420e57289e18 ("virtio-spi: Add support for virtio SPI"). Change-Id: Ia74651c8689ab993adaeb44f62757b40c23b8509 Signed-off-by: Haixu Cui Signed-off-by: Wei Liu --- drivers/spi/Kconfig | 9 ++ drivers/spi/Makefile | 1 + drivers/spi/virtio-spi.c | 299 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 309 insertions(+) create mode 100644 drivers/spi/virtio-spi.c diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 3367b81d8fde..0e3c9412b1a6 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -835,6 +835,15 @@ config SPI_MSM_GENI This driver can also be built as a module. If so, the module will be called spi-msm-geni. +config VIRTIO_SPI + tristate "VirtIO SPI driver" + depends on VIRTIO + help + This spi driver for virtio. It can be used on virtual machine. + Say Y if you want to support virtual SPI for the build-in SPI + interface. This driver can also be built as a module. If so, + the module will be called virtio-spi. + config SPI_S3C24XX tristate "Samsung S3C24XX series SPI" depends on ARCH_S3C24XX diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 8a4564d23ca4..c6c3f50e6181 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -103,6 +103,7 @@ obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o obj-$(CONFIG_SPI_QCOM_GENI) += spi-geni-qcom.o obj-$(CONFIG_SPI_MSM_GENI) += spi-msm-geni.o +obj-$(CONFIG_VIRTIO_SPI) += virtio-spi.o obj-$(CONFIG_SPI_QCOM_QSPI) += spi-qcom-qspi.o obj-$(CONFIG_SPI_QUP) += spi-qup.o obj-$(CONFIG_SPI_ROCKCHIP) += spi-rockchip.o diff --git a/drivers/spi/virtio-spi.c b/drivers/spi/virtio-spi.c new file mode 100644 index 000000000000..668f56d13e0e --- /dev/null +++ b/drivers/spi/virtio-spi.c @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Virtio ID of SPI: 0xC009 */ +#define VIRTIO_ID_SPI 49161 + +#define VIRTIO_CONFIG_SPI_BUS_NUMBER 0 +#define VIRTIO_CONFIG_SPI_CS_MAX_NUMBER 4 + +#define VIRTIO_SPI_MSG_OK 0 + +/** + * the structure define the transfer configuration: + * + * mode: how data is clocked out and in + * freq: transfer clock rate + * slave_id: chip select index the transfer used + * bits_per_word: transfer word size + * word_delay_usecs: how long to wait between words within one transfer + * cs_change: deselect device before starting the next transfer + */ +struct spi_transfer_head { + u32 mode; + u32 freq; + u8 slave_id; + u8 bits_per_word; + u8 word_delay_usecs; + u8 cs_change; +}; + +/** + * the structure define the transfer result: + * + * result: return value from backend + */ +struct spi_transfer_end { + u8 result; +}; + +/** + * the structure sent to the backend, including 2 parts: + * -- head: the configuration of the transfer + * -- end: the transfer result set by the backend + * + * note: don't need allocate transfer buffer for the request struct. + * virtio-spi based on spidev driver, when open the spidev device, + * the driver will allocate the tx_buf and rx_buf for spidev device. + * For more details, please refer to the spidev_open function in spidev.c + */ +struct virtio_spi_req { + struct spi_transfer_head head; + struct spi_transfer_end end; +}; + +/** + * struct virtio_spi - virtio spi device + * @spi: spi controller + * @vdev: the virtio device + * @spi_req: description of the fromat of transfer data + * @vq: spi virtqueue + */ +struct virtio_spi { + struct spi_master *spi; + struct virtio_device *vdev; + struct virtio_spi_req spi_req; + struct virtqueue *vq; + wait_queue_head_t inq; +}; + +/* virtqueue incoming data interrupt IRQ */ +static void virtspi_vq_isr(struct virtqueue *vq) +{ + struct virtio_spi *vspi = vq->vdev->priv; + + wake_up(&vspi->inq); +} + +static int virtspi_init_vqs(struct virtio_spi *vspi) +{ + struct virtqueue *vqs[1]; + vq_callback_t *cbs[] = { virtspi_vq_isr }; + static const char * const names[] = { "virtspi_vq_isr" }; + int err; + + err = virtio_find_vqs(vspi->vdev, 1, vqs, cbs, names, NULL); + if (err) + return err; + vspi->vq = vqs[0]; + + return 0; +} + +static void virtspi_del_vqs(struct virtio_spi *vspi) +{ + vspi->vdev->config->del_vqs(vspi->vdev); +} + +/** + * transfer one message to the backend + */ +static int virtio_spi_transfer_one(struct spi_master *spi, struct spi_device *slv, + struct spi_transfer *xfer) +{ + struct virtio_spi *vspi = spi_controller_get_devdata(spi); + struct virtio_spi_req *spi_req = &vspi->spi_req; + + struct scatterlist outhdr, tx_bufhdr, rx_bufhdr, inhdr, *sgs[4]; + unsigned int num_out = 0, num_in = 0, len; + int err = 0; + + struct virtqueue *vq = vspi->vq; + + if ((xfer->tx_buf == NULL) && (xfer->rx_buf == NULL)) { + dev_err(&vspi->vdev->dev, "Invalid transfer buffer.\n"); + return -EINVAL; + } + + if (xfer->len < 1) { + dev_err(&vspi->vdev->dev, "Invalid transfer length.\n"); + return -EINVAL; + } + + spi_req->head.slave_id = slv->chip_select; + spi_req->head.mode = slv->mode; + spi_req->head.freq = xfer->speed_hz; + + if (xfer->bits_per_word == 0) + spi_req->head.bits_per_word = slv->bits_per_word; + else + spi_req->head.bits_per_word = xfer->bits_per_word; + + spi_req->head.cs_change = xfer->cs_change; + + /* init the head queue */ + sg_init_one(&outhdr, &spi_req->head, sizeof(spi_req->head)); + sgs[num_out++] = &outhdr; + + if (xfer->tx_buf != NULL) { + /* init the tx_buf queue */ + sg_init_one(&tx_bufhdr, xfer->tx_buf, xfer->len); + sgs[num_out++] = &tx_bufhdr; + } + + if (xfer->rx_buf != NULL) { + /* init the rx_buf queue */ + sg_init_one(&rx_bufhdr, xfer->rx_buf, xfer->len); + sgs[num_out + num_in++] = &rx_bufhdr; + } + + /* init the result queue */ + sg_init_one(&inhdr, &spi_req->end, sizeof(spi_req->end)); + sgs[num_out + num_in++] = &inhdr; + + /* call the virtqueue function */ + err = virtqueue_add_sgs(vq, sgs, num_out, num_in, spi_req, GFP_KERNEL); + if (err) + return -EIO; + + /* Tell Host to go! */ + err = virtqueue_kick(vq); + if (!err) + return -EIO; + + wait_event(vspi->inq, virtqueue_get_buf(vq, &len)); + + if (spi_req->end.result != VIRTIO_SPI_MSG_OK) + err = -EIO; + else + err = 0; + + return err; +} + +/** + * used to allocate and register the spi controller + */ +static int virtspi_init_hw(struct virtio_device *vdev, + struct virtio_spi *vspi) +{ + int err; + + /* allocate the spi controller */ + vspi->spi = __spi_alloc_controller(&vdev->dev, 0, 0); + if (!vspi->spi) + return -ENOMEM; + + spi_controller_set_devdata(vspi->spi, vspi); + + vspi->spi->dev.of_node = vdev->dev.parent->of_node; + + /* read the bus number from the config space with offset 0 */ + vspi->spi->bus_num = virtio_cread32(vdev, VIRTIO_CONFIG_SPI_BUS_NUMBER); + + /* read the max chip select number from the config space with offset 4 */ + vspi->spi->num_chipselect = virtio_cread32(vdev, VIRTIO_CONFIG_SPI_CS_MAX_NUMBER); + + vspi->spi->mode_bits = (SPI_CPHA | SPI_CPOL | SPI_CS_HIGH + | SPI_LSB_FIRST | SPI_3WIRE | SPI_LOOP + | SPI_NO_CS | SPI_READY | SPI_TX_DUAL + | SPI_TX_QUAD | SPI_RX_DUAL | SPI_RX_QUAD); + vspi->spi->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 32); + + vspi->spi->transfer_one = virtio_spi_transfer_one; + vspi->spi->auto_runtime_pm = false; + + /* register the spi controller */ + err = spi_register_controller(vspi->spi); + if (err) + return err; + + return 0; +} + +static int virtspi_probe(struct virtio_device *vdev) +{ + struct virtio_spi *vspi; + int err = 0; + + if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) + return -ENODEV; + + vspi = kzalloc(sizeof(*vspi), GFP_KERNEL); + if (!vspi) + return -ENOMEM; + + vspi->vdev = vdev; + vdev->priv = vspi; + init_waitqueue_head(&vspi->inq); + + err = virtspi_init_vqs(vspi); + if (err) + goto err_init_vq; + + virtio_device_ready(vdev); + + virtqueue_enable_cb(vspi->vq); + + err = virtspi_init_hw(vdev, vspi); + if (err) + goto err_init_hw; + + return 0; + +err_init_hw: + virtspi_del_vqs(vspi); +err_init_vq: + kfree(vspi); + return err; +} + +static void virtspi_remove(struct virtio_device *vdev) +{ + struct virtio_spi *vspi = vdev->priv; + + spi_unregister_controller(vspi->spi); + + vdev->config->reset(vdev); + + virtspi_del_vqs(vspi); + kfree(vspi); +} + +static unsigned int features[] = { + /* none */ +}; +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_SPI, VIRTIO_DEV_ANY_ID }, + { }, +}; +MODULE_DEVICE_TABLE(virtio, id_table); + +static struct virtio_driver virtio_spi_driver = { + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), + .id_table = id_table, + .probe = virtspi_probe, + .remove = virtspi_remove, +}; + +module_virtio_driver(virtio_spi_driver); + +MODULE_DESCRIPTION("Virtio spi frontend driver"); +MODULE_LICENSE("GPL");