[spi-devel-general] [PATCH] Simple driver for Xilinx SPI controler.

Andrei Konovalov akonovalov at ru.mvista.com
Fri Jun 8 04:39:55 EST 2007


Hi David,

Thanks for reviewing this stuff!

David Brownell wrote:
> On Wednesday 06 June 2007, Andrei Konovalov wrote:
>> Would be nice to get this driver into mainline.
>> Reviews and comments are welcome.
> 
> I'll ignore the Kconfig flamage ... ;)
> 
> 
>> --- /dev/null
>> +++ b/drivers/spi/xilinx_spi.c
>> @@ -0,0 +1,447 @@
>> +/*
>> + * xilinx_spi.c
>> + *
>> + * Xilinx SPI controler driver (master mode only)
>> + *
>> + * Author: MontaVista Software, Inc.
>> + *         source at mvista.com
>> + *
>> + * 2002-2007 (c) MontaVista Software, Inc.  This file is licensed under the
>> + * terms of the GNU General Public License version 2.  This program is licensed
>> + * "as is" without any warranty of any kind, whether express or implied.
>> + */
>> +
>> +
>> +/* Simple macros to get the code more readable */
>> +#define xspi_in16(addr)		in_be16((u16 __iomem *)(addr))
>> +#define xspi_in32(addr)		in_be32((u32 __iomem *)(addr))
>> +#define xspi_out16(addr, value)	out_be16((u16 __iomem *)(addr), (value))
>> +#define xspi_out32(addr, value)	out_be32((u32 __iomem *)(addr), (value))
> 
> I'm rather used to seeing I/O addressses passed around as "void __iomem *"
> so those sorts of cast are not needed...  :)

I've decided to make the base_address (u8 __iomem *) to make it easier to
add the register offsets to the base. Like that:

static void xspi_init_hw(u8 __iomem *regs_base)
{
	/* Reset the SPI device */
	xspi_out32(regs_base + XIPIF_V123B_RESETR_OFFSET,
		   XIPIF_V123B_RESET_MASK);
...

Without the cast, out_be32(regs_base + XIPIF_V123B_RESETR_OFFSET, XIPIF_V123B_RESET_MASK)
produces a warning.

Another solution could be

#define XSPI_REG(base, offset) (void *)((base) + (offset))
and
	/* Reset the SPI device */
	out_be32(XSPI_REG(regs_base, XIPIF_V123B_RESETR_OFFSET),
	         XIPIF_V123B_RESET_MASK);

Is it better?

>> +
>> +static void xspi_abort_transfer(u8 __iomem *regs_base)
>> +{
> 
> You should not need an abort primitive.  This is called only
> in the remove-controller path.  By the time it's called,
> every child spi_device on this bus segment should have been
> removed ... which means any spi_driver attached to that
> device has already returned from its remove() method, which
> in turn means that there will be no spi_message objects in
> flight from any of those drivers.

I am paranoid enough not to rely 100% on the spi_device's and the spi_bitbang
doing all that :)
Agreed, xspi_abort_transfer is bad name here. But I still would like to
leave these two writes:

+	/* Deselect the slave on the SPI bus */
+	xspi_out32(regs_base + XSPI_SSR_OFFSET, 0xffff);
+	/* Disable the transmitter */
+	xspi_out16(regs_base + XSPI_CR_OFFSET,
+		   XSPI_CR_TRANS_INHIBIT | XSPI_CR_MANUAL_SSELECT);

in xilinx_spi_remove(). Just in case :)

>> +static void xilinx_spi_chipselect(struct spi_device *spi, int is_on)
>> +{
>> +	struct xilinx_spi *xspi;
>> +	u8 __iomem *regs_base;
>> +
>> +	xspi = spi_master_get_devdata(spi->master);
>> +	regs_base = xspi->regs;
>> +
>> +	if (is_on == BITBANG_CS_INACTIVE) {
>> +		/* Deselect the slave on the SPI bus */
>> +		xspi_out32(regs_base + XSPI_SSR_OFFSET, 0xffff);
> 
> I take it you can't support SPI_CS_HIGH??

There is no clear indication in the SPI controller docs that SPI_CS_HIGH
would work. I suspect it would as we don't use the ability of the controller
to generate the chip selects automatically (the auto mode doesn't support SPI_CS_HIGH).
But it is more safe to advertise SPI_CS_HIGH is not supported.

>> +/* spi_bitbang requires custom setup_transfer() to be defined if there is a
>> + * custom txrx_bufs(). We have nothing to setup here as the SPI IP block
>> + * supports just 8 bits per word, and SPI clock can't be changed in software.
>> + * Check for 8 bits per word; speed_hz checking could be added if the SPI
>> + * clock information is available. Chip select delay calculations could be
>> + * added here as soon as bitbang_work() can be made aware of the delay value.
>> + */
>> +static int xilinx_spi_setup_transfer(struct spi_device *spi,
>> +				     struct spi_transfer *t)
>> +{
>> +	u8 bits_per_word;
>> +
>> +	bits_per_word = (t) ? t->bits_per_word : spi->bits_per_word;
>> +	if (bits_per_word != 8)
>> +		return -EINVAL;
> 
> Speed checking *SHOULD* be added; the clock info can be platform data.
> 
> It would make trouble if a driver needed to turn the clock down to,
> say, 400 KHz for some operation, and the controller said "yes" but
> kept it at 25 MHz or whatever.  It's OK if the driver is told that
> 400 KHz can't happen though -- it's possible to recover from that.

OK

> (Although in practice it's best to have the transfer method do
> the error checking, so that messages that will fail do so before
> they are allowed to enter the I/O queue.)
> 
> ISTR you may need to delegate to the default method here too, but
> it's been a while since I poked at that level and the issue might
> not apply to this particular driver config.
> 
> 
> 
>> +
>> +	return 0;
>> +}
>> +
>> +
>> +static int xilinx_spi_setup(struct spi_device *spi)
>> +{
>> +	struct spi_bitbang *bitbang;
>> +	struct xilinx_spi *xspi;
>> +	int retval;
>> +
>> +	xspi = spi_master_get_devdata(spi->master);
>> +	bitbang = &xspi->bitbang;
> 
> You need to verify ALL the input parameters.  In particular,
> mask spi->mode against all the values this driver recognizes
> and supports.  If you don't support SPI_LSB_FIRST it's a bug
> if setup() succeeds after setting that.  Same thing with all
> other bits defined today (SPI_3WIRE, SPI_CS_HIGH) and in the
> future... 
> 
> For an example, see the atmel_spi.c driver and MODEBITS.
> There's a patch in MM that makes all the SPI controller
> drivers have analagous tests ... and for bitbang there's
> a patch adding spi_bitbang.mode flags to declare any
> extra spi->mode flags that should be accepted.

OK. Thanks.

>> +	...
>> +	
>> +static irqreturn_t xilinx_spi_irq(int irq, void *dev_id)
>> +{
>> +	struct xilinx_spi *xspi;
>> +	u8 __iomem *regs_base;
>> +	u32 ipif_isr;
>> +
>> +	xspi = (struct xilinx_spi *) dev_id;
>> +	regs_base = xspi->regs;
>> +
>> +     	/* Get the IPIF inetrrupts, and clear them immediately */
> 
> Spell checkers will tell you this is "interrupts" ... ;)

Oops...

>> +	ipif_isr = xspi_in32(regs_base + XIPIF_V123B_IISR_OFFSET);
>> +	xspi_out32(regs_base + XIPIF_V123B_IISR_OFFSET, ipif_isr);
>> +
>> +	if (ipif_isr & XSPI_INTR_TX_EMPTY) {	/* Transmission completed */
>> +		u16 cr;
>> +		u8 sr;
>> +
>> +		/* A transmit has just completed. Process received data and
>> +		 * check for more data to transmit. Always inhibit the
>> +		 * transmitter while the Isr refills the transmit register/FIFO,
>> +		 * or make sure it is stopped if we're done.
>> +	         */
>> +		cr = xspi_in16(regs_base + XSPI_CR_OFFSET);
>> +		xspi_out16(regs_base + XSPI_CR_OFFSET,
>> +			   cr | XSPI_CR_TRANS_INHIBIT);
>> +
>> +		/* Read out all the data from the Rx FIFO */
>> +		sr = in_8(regs_base + XSPI_SR_OFFSET);
>> +		while ((sr & XSPI_SR_RX_EMPTY_MASK) == 0) {
>> +			u8 data;
>> +
>> +			data = in_8(regs_base + XSPI_RXD_OFFSET);
>> +			if (xspi->rx_ptr) {
>> +				*xspi->rx_ptr++ = data;
>> +			}
>> +			sr = in_8(regs_base + XSPI_SR_OFFSET);
>> +		}
>> +
>> +	        /* See if there is more data to send */
>> +		if (xspi->remaining_bytes > 0) {
>> +			/* sr content is valid here; no need for io_8() */
>> +			while ((sr & XSPI_SR_TX_FULL_MASK) == 0
>> +				&& xspi->remaining_bytes > 0) {
>> +				if (xspi->tx_ptr) {
>> +					out_8(regs_base + XSPI_TXD_OFFSET,
>> +					      *xspi->tx_ptr++);
>> +				} else {
>> +					out_8(regs_base + XSPI_TXD_OFFSET, 0);
>> +				}
> 
> This duplicates the loop in txrx_bufs(); that's bad style.
> Have one routine holding the shared code.

OK

>> +static int __init xilinx_spi_probe(struct platform_device *dev)
>> +{
>> +	int ret = 0;
>> +	struct spi_master *master;
>> +	struct xilinx_spi *xspi;
>> +	struct xspi_platform_data *pdata;
>> +	struct resource *r;
>> +
>> +	/* Get resources(memory, IRQ) associated with the device */
>> +	master = spi_alloc_master(&dev->dev, sizeof(struct xilinx_spi));
>> +
>> +	if (master == NULL) {
>> +		return -ENOMEM;
>> +	}
>> +
>> +	platform_set_drvdata(dev, master);
>> +	pdata = dev->dev.platform_data;
>> +
>> +	if (pdata == NULL) {
>> +		ret = -ENODEV;
>> +		goto put_master;
>> +	}
>> +
>> +	r = platform_get_resource(dev, IORESOURCE_MEM, 0);
>> +	if (r == NULL) {
>> +		ret = -ENODEV;
>> +		goto put_master;
>> +	}
>> +
>> +	xspi = spi_master_get_devdata(master);
>> +	xspi->bitbang.master = spi_master_get(master);
>> +	xspi->bitbang.chipselect = xilinx_spi_chipselect;
>> +	xspi->bitbang.setup_transfer = xilinx_spi_setup_transfer;
>> +	xspi->bitbang.txrx_bufs = xilinx_spi_txrx_bufs;
>> +	xspi->bitbang.master->setup = xilinx_spi_setup;
>> +	init_completion(&xspi->done);
>> +
>> +	xspi->regs = ioremap(r->start, r->end - r->start + 1);
> 
> Strictly speaking a request_region() should precede the ioremap,
> but a lot of folk don't bother.  However, lacking that I'd put
> the request_irq() earlier, since that will be the only resource
> providing any guard against another driver sharing the hardware.

Will add request_region(). I was confused by platform_device_register()
doing something with the resources (contrary to the "plain" device_register()).


Thanks,
Andrei



More information about the Linuxppc-embedded mailing list