On Tue, 28 May 2019 at 11:27, Masahisa Kojima <masahisa.kojima@xxxxxxxxxx> wrote: > > This patch adds support for controller found on synquacer platforms. > > Signed-off-by: Masahisa Kojima <masahisa.kojima@xxxxxxxxxx> > Signed-off-by: Jassi Brar <jaswinder.singh@xxxxxxxxxx> > --- > drivers/spi/Kconfig | 11 + > drivers/spi/Makefile | 1 + > drivers/spi/spi-synquacer.c | 826 ++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 838 insertions(+) > create mode 100644 drivers/spi/spi-synquacer.c > > diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig > index 0fba8f400c59..24a3eac72d12 100644 > --- a/drivers/spi/Kconfig > +++ b/drivers/spi/Kconfig > @@ -732,6 +732,17 @@ config SPI_SUN6I > help > This enables using the SPI controller on the Allwinner A31 SoCs. > > +config SPI_SYNQUACER > + tristate "Socionext's Synquacer HighSpeed SPI controller" > + depends on ARCH_SYNQUACER || COMPILE_TEST > + select SPI_BITBANG Do we really need this dependency? > + help > + SPI driver for Socionext's High speed SPI controller which provides > + various operating modes for interfacing to serial peripheral devices > + that use the de-facto standard SPI protocol. > + > + It also supports the new dual-bit and quad-bit SPI protocol. > + > config SPI_MXIC > tristate "Macronix MX25F0A SPI controller" > depends on SPI_MASTER > diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile > index f2f78d03dc28..63dcab552bcb 100644 > --- a/drivers/spi/Makefile > +++ b/drivers/spi/Makefile > @@ -106,6 +106,7 @@ obj-$(CONFIG_SPI_STM32_QSPI) += spi-stm32-qspi.o > obj-$(CONFIG_SPI_ST_SSC4) += spi-st-ssc4.o > obj-$(CONFIG_SPI_SUN4I) += spi-sun4i.o > obj-$(CONFIG_SPI_SUN6I) += spi-sun6i.o > +obj-$(CONFIG_SPI_SYNQUACER) += spi-synquacer.o > obj-$(CONFIG_SPI_TEGRA114) += spi-tegra114.o > obj-$(CONFIG_SPI_TEGRA20_SFLASH) += spi-tegra20-sflash.o > obj-$(CONFIG_SPI_TEGRA20_SLINK) += spi-tegra20-slink.o > diff --git a/drivers/spi/spi-synquacer.c b/drivers/spi/spi-synquacer.c > new file mode 100644 > index 000000000000..27a9babaffc0 > --- /dev/null > +++ b/drivers/spi/spi-synquacer.c > @@ -0,0 +1,826 @@ ... > +static int synquacer_spi_probe(struct platform_device *pdev) > +{ > + struct device_node *np = pdev->dev.of_node; > + struct spi_master *master; > + struct synquacer_spi *sspi; > + int ret; > + int rx_irq, tx_irq; > + > + master = spi_alloc_master(&pdev->dev, sizeof(*sspi)); > + if (!master) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, master); > + > + sspi = spi_master_get_devdata(master); > + sspi->dev = &pdev->dev; > + > + init_completion(&sspi->transfer_done); > + > + sspi->regs = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(sspi->regs)) { > + ret = PTR_ERR(sspi->regs); > + goto put_spi; > + } > + > + sspi->clk_src_type = SYNQUACER_HSSPI_CLOCK_SRC_IHCLK; /* Default */ > + device_property_read_u32(&pdev->dev, "socionext,ihclk-rate", > + &master->max_speed_hz); /* for ACPI */ > + > + if (dev_of_node(&pdev->dev)) { > + if (device_property_match_string(&pdev->dev, > + "clock-names", "iHCLK") >= 0) { > + sspi->clk_src_type = SYNQUACER_HSSPI_CLOCK_SRC_IHCLK; > + sspi->clk = devm_clk_get(sspi->dev, "iHCLK"); > + } else if (device_property_match_string(&pdev->dev, > + "clock-names", "iPCLK") >= 0) { > + sspi->clk_src_type = SYNQUACER_HSSPI_CLOCK_SRC_IPCLK; > + sspi->clk = devm_clk_get(sspi->dev, "iPCLK"); > + } else { > + dev_err(&pdev->dev, "specified wrong clock source\n"); > + ret = -EINVAL; > + goto put_spi; > + } > + if (IS_ERR(sspi->clk)) { You could have received an -EPROBE_DEFER return value here, in which case you should not print an error and just return. > + dev_err(&pdev->dev, "clock not found\n"); > + ret = PTR_ERR(sspi->clk); > + goto put_spi; > + } > + > + ret = clk_prepare_enable(sspi->clk); > + if (ret) { > + dev_err(&pdev->dev, "failed to enable clock (%d)\n", > + ret); > + goto put_spi; > + } > + > + master->max_speed_hz = clk_get_rate(sspi->clk); > + } > + > + if (!master->max_speed_hz) { > + dev_err(&pdev->dev, "missing clock source\n"); > + return -EINVAL; > + } > + master->min_speed_hz = master->max_speed_hz / 254; > + > + sspi->aces = device_property_read_bool(&pdev->dev, > + "socionext,set-aces"); > + sspi->rtm = device_property_read_bool(&pdev->dev, "socionext,use-rtm"); > + > + master->num_chipselect = SYNQUACER_HSSPI_NUM_CHIP_SELECT; > + > + rx_irq = platform_get_irq(pdev, 0); > + if (rx_irq < 0) { <= 0 > + dev_err(&pdev->dev, "get rx_irq failed (%d)\n", rx_irq); > + ret = rx_irq; > + goto put_spi; > + } > + snprintf(sspi->rx_irq_name, SYNQUACER_HSSPI_IRQ_NAME_MAX, "%s-%s", Why not just use "%s-rx" as the format string? > + dev_name(&pdev->dev), "rx"); > + ret = devm_request_irq(&pdev->dev, rx_irq, sq_spi_rx_handler, > + 0, sspi->rx_irq_name, sspi); > + if (ret) { > + dev_err(&pdev->dev, "request rx_irq failed (%d)\n", ret); > + goto put_spi; > + } > + > + tx_irq = platform_get_irq(pdev, 1); > + if (tx_irq < 0) { <= 0 > + dev_err(&pdev->dev, "get tx_irq failed (%d)\n", tx_irq); > + ret = tx_irq; > + goto put_spi; > + } > + snprintf(sspi->tx_irq_name, SYNQUACER_HSSPI_IRQ_NAME_MAX, "%s-%s", Likewise > + dev_name(&pdev->dev), "tx"); > + ret = devm_request_irq(&pdev->dev, tx_irq, sq_spi_tx_handler, > + 0, sspi->tx_irq_name, sspi); > + if (ret) { > + dev_err(&pdev->dev, "request tx_irq failed (%d)\n", ret); > + goto put_spi; > + } > + > + master->dev.of_node = np; please add master->dev.fwnode = pdev->dev.fwnode; here as well > + master->auto_runtime_pm = true; > + master->bus_num = pdev->id; > + > + master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_TX_DUAL | SPI_RX_DUAL | > + SPI_TX_QUAD | SPI_RX_QUAD; > + master->bits_per_word_mask = SPI_BPW_MASK(32) | SPI_BPW_MASK(24) | > + SPI_BPW_MASK(16) | SPI_BPW_MASK(8); > + > + master->set_cs = synquacer_spi_set_cs; > + master->transfer_one = synquacer_spi_transfer_one; > + > + ret = synquacer_spi_enable(master); > + if (ret) > + goto fail_enable; > + > + pm_runtime_set_active(sspi->dev); > + pm_runtime_enable(sspi->dev); > + > + ret = devm_spi_register_master(sspi->dev, master); > + if (ret) > + goto disable_pm; > + > + return 0; > + > +disable_pm: > + pm_runtime_disable(sspi->dev); > +fail_enable: > + if (!IS_ERR(sspi->clk)) You can drop this check: clk_disable_unprepare() already ignores NULL or ERR values. > + clk_disable_unprepare(sspi->clk); > +put_spi: > + spi_master_put(master); > + > + return ret; > +} > + > +static int synquacer_spi_remove(struct platform_device *pdev) > +{ > + struct spi_master *master = platform_get_drvdata(pdev); > + struct synquacer_spi *sspi = spi_master_get_devdata(master); > + > + pm_runtime_disable(sspi->dev); > + > + if (!IS_ERR(sspi->clk)) > + clk_disable_unprepare(sspi->clk); > + Same here > + return 0; > +} > + > +static int __maybe_unused synquacer_spi_suspend(struct device *dev) > +{ > + struct spi_master *master = dev_get_drvdata(dev); > + struct synquacer_spi *sspi = spi_master_get_devdata(master); > + int ret; > + > + ret = spi_master_suspend(master); > + if (ret) > + return ret; > + > + if (!pm_runtime_suspended(dev)) > + if (!IS_ERR(sspi->clk)) > + clk_disable_unprepare(sspi->clk); > + ... and here > + return ret; > +} > + > +static int __maybe_unused synquacer_spi_resume(struct device *dev) > +{ > + struct spi_master *master = dev_get_drvdata(dev); > + struct synquacer_spi *sspi = spi_master_get_devdata(master); > + int ret; > + > + if (!pm_runtime_suspended(dev)) { > + /* Ensure reconfigure during next xfer */ > + sspi->speed = 0; > + > + if (!IS_ERR(sspi->clk)) { ... and here > + ret = clk_prepare_enable(sspi->clk); > + if (ret < 0) { > + dev_err(dev, "failed to enable clk (%d)\n", > + ret); > + return ret; > + } > + } > + > + ret = synquacer_spi_enable(master); > + if (ret) { > + dev_err(dev, "failed to enable spi (%d)\n", ret); > + return ret; > + } > + } > + > + ret = spi_master_resume(master); > + if (ret < 0) { > + if (!IS_ERR(sspi->clk)) > + clk_disable_unprepare(sspi->clk); .. and here > + } > + > + return ret; > +} > + > +static SIMPLE_DEV_PM_OPS(synquacer_spi_pm_ops, synquacer_spi_suspend, > + synquacer_spi_resume); > + > +static const struct of_device_id synquacer_spi_of_match[] = { > + {.compatible = "socionext,synquacer-spi"}, > + {} > +}; > +MODULE_DEVICE_TABLE(of, synquacer_spi_of_match); > + > +#ifdef CONFIG_ACPI > +static const struct acpi_device_id synquacer_hsspi_acpi_ids[] = { > + { "SCX0004" }, > + { /* sentinel */ } > +}; > +MODULE_DEVICE_TABLE(acpi, synquacer_hsspi_acpi_ids); > +#endif > + > +static struct platform_driver synquacer_spi_driver = { > + .driver = { > + .name = "synquacer-spi", > + .pm = &synquacer_spi_pm_ops, > + .of_match_table = synquacer_spi_of_match, > + .acpi_match_table = ACPI_PTR(synquacer_hsspi_acpi_ids), > + }, > + .probe = synquacer_spi_probe, > + .remove = synquacer_spi_remove, > +}; > +module_platform_driver(synquacer_spi_driver); > + > +MODULE_DESCRIPTION("Socionext Synquacer HS-SPI controller driver"); > +MODULE_AUTHOR("Masahisa Kojima <masahisa.kojima@xxxxxxxxxx>"); > +MODULE_AUTHOR("Jassi Brar <jaswinder.singh@xxxxxxxxxx>"); > +MODULE_LICENSE("GPL v2"); > -- > 2.14.2 > With the changes above, Reviewed-by: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx> but someone else has to review the SPI bits.