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 <quic_haixcui@quicinc.com> Signed-off-by: Wei Liu <quic_wliu8@quicinc.com>
This commit is contained in:
parent
dab6a48ab5
commit
503efdfd8c
@ -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
|
||||
|
@ -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
|
||||
|
299
drivers/spi/virtio-spi.c
Normal file
299
drivers/spi/virtio-spi.c
Normal file
@ -0,0 +1,299 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/virtio.h>
|
||||
#include <uapi/linux/virtio_ids.h>
|
||||
#include <linux/virtio_config.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
/* 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");
|
Loading…
Reference in New Issue
Block a user