[PATCH 5/7] spi: zynqmp-gqspi: Split the bus
Mahapatra, Amit Kumar
amit.kumar-mahapatra at amd.com
Tue Jan 21 05:19:42 PST 2025
Hello Andreson,
> -----Original Message-----
> From: Sean Anderson <sean.anderson at linux.dev>
> Sent: Friday, January 17, 2025 4:51 AM
> To: Mark Brown <broonie at kernel.org>; Simek, Michal <michal.simek at amd.com>;
> linux-spi at vger.kernel.org
> Cc: Jinjie Ruan <ruanjinjie at huawei.com>; linux-arm-kernel at lists.infradead.org;
> Mahapatra, Amit Kumar <amit.kumar-mahapatra at amd.com>; linux-
> kernel at vger.kernel.org; Miquel Raynal <miquel.raynal at bootlin.com>; Sean
> Anderson <sean.anderson at linux.dev>
> Subject: [PATCH 5/7] spi: zynqmp-gqspi: Split the bus
>
> This device supports two separate SPI busses: "lower" (SPI0) and "upper"
> (SPI1). Each SPI bus has separate clock and data lines, as well as a hardware-
> controlled chip select. The busses may be driven independently, with only one bus
> active at a time, or in concert, with both busses active. If both busses are driven at
> once, data may either be duplicated on each bus or striped (bitwise) across both
> busses.
>
> The current driver does not model this situation. It exposes one bus, where CS 0
> uses the lower bus and the lower chip select, and CS 1 uses the upper bus and the
> upper chip select. It is not possible to use the upper chip select with the lower bus
> (or vice versa). GPIO chip selects are unsupported, and there would be no way to
> specify which bus to use if they were.
>
> To conserve pins, designers may wish to place multiple devices on a single SPI bus.
> Add support for this by splitting the "merged" bus into an upper and lower bus. Each
> bus uses a separate devicetree node and has a single native chipselect 0. If "lower"
IMHO, restricting users to fixed names is not ideal. A better approach would be to
introduce a Device Tree (DT) property for the bus number and select the bus
accordingly.
Regards,
Amit
> and "upper" nodes are absent from the devicetree, we register the merged bus
> instead, which maintains the current behavior.
>
> Signed-off-by: Sean Anderson <sean.anderson at linux.dev>
> ---
>
> drivers/spi/spi-zynqmp-gqspi.c | 155 ++++++++++++++++++++++++++-------
> 1 file changed, 125 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/spi/spi-zynqmp-gqspi.c b/drivers/spi/spi-zynqmp-gqspi.c index
> d78e114e17e0..9823d710c4d6 100644
> --- a/drivers/spi/spi-zynqmp-gqspi.c
> +++ b/drivers/spi/spi-zynqmp-gqspi.c
> @@ -167,6 +167,10 @@ struct qspi_platform_data {
>
> /**
> * struct zynqmp_qspi - Defines qspi driver instance
> + * @lower Pointer to "lower" SPI bus
> + * @upper Pointer to "upper" SPI bus
> + * @merged Pointer to legacy SPI bus which is a combination of
> + * @lower and @upper
> * @ctlr: Pointer to the spi controller information
> * @regs: Virtual address of the QSPI controller registers
> * @refclk: Pointer to the peripheral clock
> @@ -191,7 +195,7 @@ struct qspi_platform_data {
> * @has_tapdelay: Used for tapdelay register available in qspi
> */
> struct zynqmp_qspi {
> - struct spi_controller *ctlr;
> + struct spi_controller *lower, *upper, *merged;
> void __iomem *regs;
> struct clk *refclk;
> struct clk *pclk;
> @@ -467,20 +471,33 @@ static void zynqmp_qspi_copy_read_data(struct
> zynqmp_qspi *xqspi,
> */
> static void zynqmp_qspi_chipselect(struct spi_device *qspi, bool is_high) {
> - struct zynqmp_qspi *xqspi = spi_controller_get_devdata(qspi->controller);
> + struct spi_controller *ctlr = qspi->controller;
> + struct zynqmp_qspi *xqspi = spi_controller_get_devdata(ctlr);
> ulong timeout;
> u32 genfifoentry = 0, statusreg;
>
> genfifoentry |= GQSPI_GENFIFO_MODE_SPI;
>
> if (!is_high) {
> - if (!spi_get_chipselect(qspi, 0)) {
> - xqspi->genfifobus = GQSPI_GENFIFO_BUS_LOWER;
> - xqspi->genfifocs = GQSPI_GENFIFO_CS_LOWER;
> + bool upper;
> +
> + if (ctlr == xqspi->lower) {
> + upper = false;
> + } else if (ctlr == xqspi->upper) {
> + upper = true;
> } else {
> + WARN_ON_ONCE(ctlr != xqspi->merged);
> + upper = spi_get_chipselect(qspi, 0);
> + }
> +
> + if (upper) {
> xqspi->genfifobus = GQSPI_GENFIFO_BUS_UPPER;
> xqspi->genfifocs = GQSPI_GENFIFO_CS_UPPER;
> + } else {
> + xqspi->genfifobus = GQSPI_GENFIFO_BUS_LOWER;
> + xqspi->genfifocs = GQSPI_GENFIFO_CS_LOWER;
> }
> +
> genfifoentry |= xqspi->genfifobus;
> genfifoentry |= xqspi->genfifocs;
> genfifoentry |= GQSPI_GENFIFO_CS_SETUP; @@ -962,12
> +979,28 @@ static int zynqmp_qspi_read_op(struct zynqmp_qspi *xqspi, u8
> rx_nbits, static int __maybe_unused zynqmp_qspi_suspend(struct device *dev) {
> struct zynqmp_qspi *xqspi = dev_get_drvdata(dev);
> - struct spi_controller *ctlr = xqspi->ctlr;
> int ret;
>
> - ret = spi_controller_suspend(ctlr);
> - if (ret)
> - return ret;
> + if (xqspi->merged) {
> + ret = spi_controller_suspend(xqspi->merged);
> + if (ret)
> + return ret;
> + } else {
> + if (xqspi->lower) {
> + ret = spi_controller_suspend(xqspi->lower);
> + if (ret)
> + return ret;
> + }
> +
> + if (xqspi->upper) {
> + ret = spi_controller_suspend(xqspi->upper);
> + if (ret) {
> + if (xqspi->lower)
> + spi_controller_resume(xqspi->lower);
> + return ret;
> + }
> + }
> + }
>
> zynqmp_gqspi_write(xqspi, GQSPI_EN_OFST, 0x0);
>
> @@ -986,13 +1019,18 @@ static int __maybe_unused
> zynqmp_qspi_suspend(struct device *dev) static int __maybe_unused
> zynqmp_qspi_resume(struct device *dev) {
> struct zynqmp_qspi *xqspi = dev_get_drvdata(dev);
> - struct spi_controller *ctlr = xqspi->ctlr;
> + int ret = 0;
>
> zynqmp_gqspi_write(xqspi, GQSPI_EN_OFST, GQSPI_EN_MASK);
>
> - spi_controller_resume(ctlr);
> + if (xqspi->merged)
> + ret = spi_controller_resume(xqspi->merged);
> + if (xqspi->lower)
> + ret = spi_controller_resume(xqspi->lower) ?: ret;
> + if (xqspi->upper)
> + ret = spi_controller_resume(xqspi->upper) ?: ret;
>
> - return 0;
> + return ret;
> }
>
> /**
> @@ -1253,6 +1291,41 @@ static const struct spi_controller_mem_ops
> zynqmp_qspi_mem_ops = {
> .exec_op = zynqmp_qspi_exec_op,
> };
>
> +static void zynqmp_qspi_release_node(void *of_node) {
> + of_node_put(of_node);
> +}
> +
> +static struct spi_controller *
> +zynqmp_qspi_alloc_split(struct zynqmp_qspi *xqspi, const char *name) {
> + struct spi_controller *ctlr;
> + struct device_node *np;
> + u32 num_cs;
> + int err;
> +
> + np = of_get_child_by_name(xqspi->dev->of_node, name);
> + if (!np)
> + return NULL;
> +
> + err = devm_add_action_or_reset(xqspi->dev, zynqmp_qspi_release_node,
> + np);
> + if (err)
> + return ERR_PTR(err);
> +
> + ctlr = devm_spi_alloc_host(xqspi->dev, 0);
> + if (!ctlr)
> + return ERR_PTR(-ENOMEM);
> +
> + ctlr->dev.of_node = np;
> + if (of_property_read_u32(np, "num-cs", &num_cs))
> + ctlr->num_chipselect = GQSPI_DEFAULT_NUM_CS;
> + else
> + ctlr->num_chipselect = num_cs;
> +
> + return ctlr;
> +}
> +
> static int zynqmp_qspi_register_ctlr(struct zynqmp_qspi *xqspi,
> struct spi_controller *ctlr)
> {
> @@ -1261,6 +1334,7 @@ static int zynqmp_qspi_register_ctlr(struct zynqmp_qspi
> *xqspi,
> if (!ctlr)
> return 0;
>
> + spi_controller_set_devdata(ctlr, xqspi);
> ctlr->mode_bits = SPI_CPOL | SPI_CPHA | SPI_RX_DUAL |
> SPI_RX_QUAD |
> SPI_TX_DUAL | SPI_TX_QUAD;
> ctlr->max_speed_hz = xqspi->speed_hz;
> @@ -1287,22 +1361,47 @@ static int zynqmp_qspi_register_ctlr(struct
> zynqmp_qspi *xqspi, static int zynqmp_qspi_probe(struct platform_device *pdev) {
> int ret = 0;
> - struct spi_controller *ctlr;
> struct zynqmp_qspi *xqspi;
> struct device *dev = &pdev->dev;
> - struct device_node *np = dev->of_node;
> - u32 num_cs;
> const struct qspi_platform_data *p_data;
>
> - ctlr = devm_spi_alloc_host(&pdev->dev, sizeof(*xqspi));
> - if (!ctlr)
> + xqspi = devm_kzalloc(dev, sizeof(*xqspi), GFP_KERNEL);
> + if (!xqspi)
> return -ENOMEM;
>
> - xqspi = spi_controller_get_devdata(ctlr);
> xqspi->dev = dev;
> - xqspi->ctlr = ctlr;
> platform_set_drvdata(pdev, xqspi);
>
> + xqspi->lower = zynqmp_qspi_alloc_split(xqspi, "spi-lower");
> + if (IS_ERR(xqspi->lower))
> + return PTR_ERR(xqspi->lower);
> +
> + xqspi->upper = zynqmp_qspi_alloc_split(xqspi, "spi-upper");
> + if (IS_ERR(xqspi->upper))
> + return PTR_ERR(xqspi->upper);
> +
> + if (!xqspi->lower && !xqspi->upper) {
> + struct spi_controller *ctlr = devm_spi_alloc_host(dev, 0);
> + u32 num_cs;
> +
> + if (!ctlr)
> + return -ENOMEM;
> +
> + ret = of_property_read_u32(dev->of_node, "num-cs", &num_cs);
> + if (ret < 0) {
> + ctlr->num_chipselect = GQSPI_DEFAULT_NUM_CS;
> + } else if (num_cs > GQSPI_MAX_NUM_CS) {
> + dev_err(dev, "only %d chip selects are available\n",
> + GQSPI_MAX_NUM_CS);
> + return -EINVAL;
> + } else {
> + ctlr->num_chipselect = num_cs;
> + }
> +
> + ctlr->dev.of_node = dev->of_node;
> + xqspi->merged = ctlr;
> + }
> +
> p_data = of_device_get_match_data(&pdev->dev);
> if (p_data && (p_data->quirks & QSPI_QUIRK_HAS_TAPDELAY))
> xqspi->has_tapdelay = true;
> @@ -1375,19 +1474,15 @@ static int zynqmp_qspi_probe(struct platform_device
> *pdev)
> if (ret)
> goto clk_dis_all;
>
> - ret = of_property_read_u32(np, "num-cs", &num_cs);
> - if (ret < 0) {
> - ctlr->num_chipselect = GQSPI_DEFAULT_NUM_CS;
> - } else if (num_cs > GQSPI_MAX_NUM_CS) {
> - ret = -EINVAL;
> - dev_err(&pdev->dev, "only %d chip selects are available\n",
> - GQSPI_MAX_NUM_CS);
> + ret = zynqmp_qspi_register_ctlr(xqspi, xqspi->lower);
> + if (ret)
> goto clk_dis_all;
> - } else {
> - ctlr->num_chipselect = num_cs;
> - }
>
> - ret = zynqmp_qspi_register_ctlr(xqspi, ctlr);
> + ret = zynqmp_qspi_register_ctlr(xqspi, xqspi->upper);
> + if (ret)
> + goto clk_dis_all;
> +
> + ret = zynqmp_qspi_register_ctlr(xqspi, xqspi->merged);
> if (ret)
> goto clk_dis_all;
>
> --
> 2.35.1.1320.gc452695387.dirty
More information about the linux-arm-kernel
mailing list