mbox series

[v6,0/2] Add spi control driver for Sunplus SP7021 SoC

Message ID cover.1642494310.git.lhjeff911@gmail.com
Headers show
Series Add spi control driver for Sunplus SP7021 SoC | expand

Message

Li-hao Kuo Jan. 18, 2022, 8:42 a.m. UTC
This is a patch series for SPI driver for Sunplus SP7021 SoC.

Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
etc.) into a single chip. It is designed for industrial control.

Refer to:
https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
https://tibbo.com/store/plus1.html

Li-hao Kuo (2):
  spi: Add spi driver for Sunplus SP7021
  dt-bindings:spi: Add Sunplus SP7021 schema

 .../bindings/spi/spi-sunplus-sp7021.yaml           |  81 +++
 MAINTAINERS                                        |   7 +
 drivers/spi/Kconfig                                |  11 +
 drivers/spi/Makefile                               |   1 +
 drivers/spi/spi-sunplus-sp7021.c                   | 602 +++++++++++++++++++++
 5 files changed, 702 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/spi/spi-sunplus-sp7021.yaml
 create mode 100644 drivers/spi/spi-sunplus-sp7021.c

Comments

Mark Brown Jan. 18, 2022, 5:41 p.m. UTC | #1
On Tue, Jan 18, 2022 at 04:42:38PM +0800, Li-hao Kuo wrote:

Looks mostly good - a couple of small nits below but nothing major.

> +static int sp7021_spi_master_transfer_one(struct spi_controller *ctlr, struct spi_device *spi,
> +				       struct spi_transfer *xfer)
> +{

> +	for (i = 0; i <= xfer_cnt; i++) {
> +		mutex_lock(&pspim->buf_lock);

This lock is redundant: it is only ever held in this function which is
guaranteed by the core to never be called twice concurrently.

> +	ret = devm_request_irq(dev, pspim->m_irq, sp7021_spi_master_irq,
> +			       IRQF_TRIGGER_RISING, pdev->name, pspim);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_request_irq(dev, pspim->s_irq, sp7021_spi_slave_irq,
> +			       IRQF_TRIGGER_RISING, pdev->name, pspim);
> +	if (ret)
> +		return ret;

Are you sure the driver is ready to handle interrupts without any of the
other resources?  Normally interrupts are one of the last things to be
requested.
Andy Shevchenko Jan. 19, 2022, 10:08 p.m. UTC | #2
On Tue, Jan 18, 2022 at 10:42 AM Li-hao Kuo <lhjeff911@gmail.com> wrote:
>
> Add spi driver for Sunplus SP7021.

...

> Changes in v6:

Thanks for update, my comments below.

...

> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/reset.h>
> +#include <linux/spi/spi.h>

...

> +       data_status = readl(pspim->s_base + SP7021_DATA_RDY_REG);
> +       writel(data_status | SP7021_SLAVE_CLR_INT, pspim->s_base + SP7021_DATA_RDY_REG);

Wouldn't be

       data_status = readl(pspim->s_base + SP7021_DATA_RDY_REG);
       data_status |= SP7021_SLAVE_CLR_INT;
       writel(data_status, pspim->s_base + SP7021_DATA_RDY_REG);

better to read? Same question to other places like this.

...

> +       writel(SP7021_SLAVE_DMA_EN | SP7021_SLAVE_DMA_RW | FIELD_PREP(SP7021_SLAVE_DMA_CMD, 3),
> +              pspim->s_base + SP7021_SLAVE_DMA_CTRL_REG);

Temporary variable?
var = ...

...

> +       writel(readl(pspim->s_base + SP7021_DATA_RDY_REG) | SP7021_SLAVE_DATA_RDY,
> +              pspim->s_base + SP7021_DATA_RDY_REG);

Ditto.

...

> +int sp7021_spi_slave_rx(struct spi_device *spi, struct spi_transfer *xfer)
> +{
> +       struct sp7021_spi_ctlr *pspim = spi_controller_get_devdata(spi->controller);

> +       int ret = 0;

Unused.

...

> +       writel(SP7021_SLAVE_DMA_EN | FIELD_PREP(SP7021_SLAVE_DMA_CMD, 3),
> +              pspim->s_base + SP7021_SLAVE_DMA_CTRL_REG);

Temporary variable?

...

> +static irqreturn_t sp7021_spi_master_irq(int irq, void *dev)
> +{
> +       struct sp7021_spi_ctlr *pspim = dev;
> +       unsigned int tx_cnt, total_len;
> +       unsigned int tx_len, rx_cnt;
> +       unsigned int fd_status;

> +       unsigned long flags;

Why do you need this?

> +       bool isrdone = false;
> +       u32 value;

> +       return IRQ_HANDLED;
> +}

...

> +       div = clk_rate / xfer->speed_hz;
> +       if (div < 2)
> +               div = 2;

div = max(2U, clk_rate / xfer->speed_hz); ?

...

> +static int sp7021_spi_master_transfer_one(struct spi_controller *ctlr, struct spi_device *spi,
> +                                      struct spi_transfer *xfer)
> +{
> +       struct sp7021_spi_ctlr *pspim = spi_master_get_devdata(ctlr);
> +       unsigned long timeout = msecs_to_jiffies(1000);
> +       unsigned int xfer_cnt, xfer_len, last_len;
> +       unsigned int i, len_temp;
> +       u32 reg_temp;

> +       int ret;

Seems redundant.

> +       xfer_cnt = xfer->len / SP7021_SPI_DATA_SIZE;
> +       last_len = xfer->len % SP7021_SPI_DATA_SIZE;
> +
> +       for (i = 0; i <= xfer_cnt; i++) {
> +               mutex_lock(&pspim->buf_lock);
> +               sp7021_prep_transfer(ctlr, spi);
> +               sp7021_spi_setup_clk(ctlr, xfer);
> +               reinit_completion(&pspim->isr_done);

> +               if (i == xfer_cnt)
> +                       xfer_len = last_len;
> +               else
> +                       xfer_len = SP7021_SPI_DATA_SIZE;
> +
> +               pspim->tx_buf = xfer->tx_buf + i * SP7021_SPI_DATA_SIZE;
> +               pspim->rx_buf = xfer->rx_buf + i * SP7021_SPI_DATA_SIZE;
> +
> +               if (pspim->tx_cur_len < xfer_len) {
> +                       len_temp = min(pspim->data_unit, xfer_len);
> +                       sp7021_spi_master_wb(pspim, len_temp);
> +               }
> +               reg_temp = readl(pspim->m_base + SP7021_SPI_CONFIG_REG);
> +               reg_temp &= ~SP7021_CLEAN_RW_BYTE;
> +               reg_temp &= ~SP7021_CLEAN_FLUG_MASK;
> +               reg_temp |= SP7021_FD_SEL | SP7021_FINISH_FLAG_MASK |
> +                           SP7021_TX_EMP_FLAG_MASK | SP7021_RX_FULL_FLAG_MASK |
> +                           FIELD_PREP(SP7021_TX_UNIT, 0) | FIELD_PREP(SP7021_RX_UNIT, 0);
> +               writel(reg_temp, pspim->m_base + SP7021_SPI_CONFIG_REG);
> +
> +               reg_temp = FIELD_PREP(SP7021_SET_TX_LEN, xfer_len) |
> +                                     FIELD_PREP(SP7021_SET_XFER_LEN, xfer_len) |
> +                                     SP7021_SPI_START_FD;
> +               writel(reg_temp, pspim->m_base + SP7021_SPI_STATUS_REG);
> +
> +               if (!wait_for_completion_interruptible_timeout(&pspim->isr_done, timeout)) {
> +                       dev_err(&spi->dev, "wait_for_completion err\n");
> +                       return -ETIMEDOUT;
> +               }
> +
> +               reg_temp = readl(pspim->m_base + SP7021_SPI_STATUS_REG);
> +               if (reg_temp & SP7021_FINISH_FLAG) {
> +                       writel(SP7021_FINISH_FLAG, pspim->m_base + SP7021_SPI_STATUS_REG);
> +                       writel(readl(pspim->m_base + SP7021_SPI_CONFIG_REG) &
> +                               SP7021_CLEAN_FLUG_MASK, pspim->m_base + SP7021_SPI_CONFIG_REG);
> +               }
> +
> +               if (pspim->xfer_conf & SP7021_CPOL_FD)
> +                       writel(pspim->xfer_conf, pspim->m_base + SP7021_SPI_CONFIG_REG);
> +
> +               mutex_unlock(&pspim->buf_lock);
> +               ret = 0;
> +       }
> +       return ret;
> +}

...

> +static int sp7021_spi_slave_transfer_one(struct spi_controller *ctlr, struct spi_device *spi,
> +                                      struct spi_transfer *xfer)
> +{
> +       struct sp7021_spi_ctlr *pspim = spi_master_get_devdata(ctlr);
> +       struct device *dev = pspim->dev;

> +       int mode, ret = 0;

ret assignment can be moved to the default case below, where it will
naturally fit.

> +       mode = SP7021_SPI_IDLE;

> +       if (xfer->tx_buf && xfer->rx_buf) {
> +               dev_dbg(&ctlr->dev, "%s() wrong command\n", __func__);
> +               ret = -EINVAL;

Do you need this check if you properly set the capabilities of the controller?
If still needed, why not return here?

> +       } else if (xfer->tx_buf) {
> +               xfer->tx_dma = dma_map_single(dev, (void *)xfer->tx_buf,
> +                                             xfer->len, DMA_TO_DEVICE);
> +               if (dma_mapping_error(dev, xfer->tx_dma))
> +                       return -ENOMEM;
> +               mode = SP7021_SLAVE_WRITE;
> +       } else if (xfer->rx_buf) {
> +               xfer->rx_dma = dma_map_single(dev, xfer->rx_buf, xfer->len,
> +                                             DMA_FROM_DEVICE);
> +               if (dma_mapping_error(dev, xfer->rx_dma))
> +                       return -ENOMEM;
> +               mode = SP7021_SLAVE_READ;
> +       }
> +
> +       switch (mode) {
> +       case SP7021_SLAVE_WRITE:
> +               ret = sp7021_spi_slave_tx(spi, xfer);
> +               break;
> +       case SP7021_SLAVE_READ:
> +               ret = sp7021_spi_slave_rx(spi, xfer);
> +               break;
> +       default:
> +               break;
> +       }
> +       if (xfer->tx_buf)
> +               dma_unmap_single(dev, xfer->tx_dma, xfer->len, DMA_TO_DEVICE);
> +       if (xfer->rx_buf)
> +               dma_unmap_single(dev, xfer->rx_dma, xfer->len, DMA_FROM_DEVICE);

Why can't you use SPI core DMA mapping code?

> +       spi_finalize_current_transfer(ctlr);
> +       return ret;
> +}

...

> +       device_set_node(&ctlr->dev, pdev->dev.fwnode);

Use dev_fwnode() in the second argument.
Lh Kuo 郭力豪 Jan. 20, 2022, 9:12 a.m. UTC | #3
Hi Mr. Mark Brown :

Thanks for your comment, I will modify it in next next submission

> 
> > +	for (i = 0; i <= xfer_cnt; i++) {
> > +		mutex_lock(&pspim->buf_lock);
> 
> This lock is redundant: it is only ever held in this function which is guaranteed by the core to never be
> called twice concurrently.

I will modify it in next next submission


> > +	ret = devm_request_irq(dev, pspim->m_irq, sp7021_spi_master_irq,
> > +			       IRQF_TRIGGER_RISING, pdev->name, pspim);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = devm_request_irq(dev, pspim->s_irq, sp7021_spi_slave_irq,
> > +			       IRQF_TRIGGER_RISING, pdev->name, pspim);
> > +	if (ret)
> > +		return ret;
> 
> Are you sure the driver is ready to handle interrupts without any of the other resources?  Normally
> interrupts are one of the last things to be requested.

I will modify it in next next submission
Lh Kuo 郭力豪 Jan. 20, 2022, 9:23 a.m. UTC | #4
Hi Andy Shevchenko :

Thanks for your comment, I have some questions as below.

> > +       if (xfer->tx_buf)
> > +               dma_unmap_single(dev, xfer->tx_dma, xfer->len, DMA_TO_DEVICE);
> > +       if (xfer->rx_buf)
> > +               dma_unmap_single(dev, xfer->rx_dma, xfer->len,
> > + DMA_FROM_DEVICE);
> 
> Why can't you use SPI core DMA mapping code?

I didn't find the SPI core DMA mapping code for single maping. 
The method currently used is the general DMA single-map code usage method.


> 
> > +       device_set_node(&ctlr->dev, pdev->dev.fwnode);
> 
> Use dev_fwnode() in the second argument.
>

You mean as below ?

device_set_node(&ctlr->dev, dev_fwnode(dev));

Best Regards,
Li-hao Kuo
Andy Shevchenko Jan. 20, 2022, 9:51 a.m. UTC | #5
On Thu, Jan 20, 2022 at 11:22 AM Lh Kuo 郭力豪 <lh.Kuo@sunplus.com> wrote:

...

> > > +       if (xfer->tx_buf)
> > > +               dma_unmap_single(dev, xfer->tx_dma, xfer->len, DMA_TO_DEVICE);
> > > +       if (xfer->rx_buf)
> > > +               dma_unmap_single(dev, xfer->rx_dma, xfer->len,
> > > + DMA_FROM_DEVICE);
> >
> > Why can't you use SPI core DMA mapping code?
>
> I didn't find the SPI core DMA mapping code for single maping.
> The method currently used is the general DMA single-map code usage method.

Why do you need single page mapping?
What's wrong with SG mapping that SPI core provides?

...

> > > +       device_set_node(&ctlr->dev, pdev->dev.fwnode);
> >
> > Use dev_fwnode() in the second argument.
>
> You mean as below ?
>
> device_set_node(&ctlr->dev, dev_fwnode(dev));

Yes.
Lh Kuo 郭力豪 Jan. 21, 2022, 6:55 a.m. UTC | #6
> > > > +       if (xfer->tx_buf)
> > > > +               dma_unmap_single(dev, xfer->tx_dma, xfer->len, DMA_TO_DEVICE);
> > > > +       if (xfer->rx_buf)
> > > > +               dma_unmap_single(dev, xfer->rx_dma, xfer->len,
> > > > + DMA_FROM_DEVICE);
> > >
> > > Why can't you use SPI core DMA mapping code?
> >
> > I didn't find the SPI core DMA mapping code for single maping.
> > The method currently used is the general DMA single-map code usage method.
> 
> Why do you need single page mapping?
> What's wrong with SG mapping that SPI core provides?

SP7021 SPI slave dma only supports single dma in one trigger. 
It is not suitable for using SG mapping.
If the length of the transfer is larger than the length of the SG-mapping, 
Slave-mode will get error in the transfer.
Andy Shevchenko Jan. 21, 2022, 10:09 a.m. UTC | #7
On Fri, Jan 21, 2022 at 8:54 AM Lh Kuo 郭力豪 <lh.Kuo@sunplus.com> wrote:
>
> > > > > +       if (xfer->tx_buf)
> > > > > +               dma_unmap_single(dev, xfer->tx_dma, xfer->len, DMA_TO_DEVICE);
> > > > > +       if (xfer->rx_buf)
> > > > > +               dma_unmap_single(dev, xfer->rx_dma, xfer->len,
> > > > > + DMA_FROM_DEVICE);
> > > >
> > > > Why can't you use SPI core DMA mapping code?
> > >
> > > I didn't find the SPI core DMA mapping code for single maping.
> > > The method currently used is the general DMA single-map code usage method.
> >
> > Why do you need single page mapping?
> > What's wrong with SG mapping that SPI core provides?
>
> SP7021 SPI slave dma only supports single dma in one trigger.
> It is not suitable for using SG mapping.
> If the length of the transfer is larger than the length of the SG-mapping,
> Slave-mode will get error in the transfer.

Same story for SPI DesignWare on Intel Medfield, where no SG transfers
are supported by hardware. Nevertheless, the DMA driver takes care of
this and on each interrupt recharges a channel to continue. Why can't
the same be implemented here?
Lh Kuo 郭力豪 Jan. 24, 2022, 3:27 a.m. UTC | #8
> > > > > > +       if (xfer->tx_buf)
> > > > > > +               dma_unmap_single(dev, xfer->tx_dma, xfer->len, DMA_TO_DEVICE);
> > > > > > +       if (xfer->rx_buf)
> > > > > > +               dma_unmap_single(dev, xfer->rx_dma, xfer->len,
> > > > > > + DMA_FROM_DEVICE);
> > > > >
> > > > > Why can't you use SPI core DMA mapping code?
> > > >
> > > > I didn't find the SPI core DMA mapping code for single maping.
> > > > The method currently used is the general DMA single-map code usage method.
> > >
> > > Why do you need single page mapping?
> > > What's wrong with SG mapping that SPI core provides?
> >
> > SP7021 SPI slave dma only supports single dma in one trigger.
> > It is not suitable for using SG mapping.
> > If the length of the transfer is larger than the length of the
> > SG-mapping, Slave-mode will get error in the transfer.
> 
> Same story for SPI DesignWare on Intel Medfield, where no SG transfers are supported by hardware.
> Nevertheless, the DMA driver takes care of this and on each interrupt recharges a channel to continue.
> Why can't the same be implemented here?
> 
> 
I think it should work in master. spi master must actively send clk and date to slave device.
And yes, in the "master" mode it can handle SG-DMA on each interrupt.
But if working in "slave" mode, the master will not know the state of the slave. Slaves work on interrupt and recharge channels.
When master send clk and date in the same time. It may lose data and errors occur
Mark Brown Jan. 25, 2022, 2:35 p.m. UTC | #9
On Tue, 18 Jan 2022 16:42:37 +0800, Li-hao Kuo wrote:
> This is a patch series for SPI driver for Sunplus SP7021 SoC.
> 
> Sunplus SP7021 is an ARM Cortex A7 (4 cores) based SoC. It integrates
> many peripherals (ex: UART, I2C, SPI, SDIO, eMMC, USB, SD card and
> etc.) into a single chip. It is designed for industrial control.
> 
> Refer to:
> https://sunplus-tibbo.atlassian.net/wiki/spaces/doc/overview
> https://tibbo.com/store/plus1.html
> 
> [...]

Applied to

   https://git.kernel.org/pub/scm/linux/kernel/git/broonie/spi.git for-next

Thanks!

[1/2] spi: Add spi driver for Sunplus SP7021
      commit: f62ca4e2a863033d9b3b5a00a0d897557c9da6c5
[2/2] dt-bindings:spi: Add Sunplus SP7021 schema
      (no commit info)

All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.

You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.

If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.

Please add any relevant lists and maintainers to the CCs when replying
to this mail.

Thanks,
Mark