mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	spi: bcm2835aux: add bcm2835 auxiliary spi device driver
The bcm2835 has 2 auxiliary spi bus masters spi1 and spi2. This implements the driver to enable these devices. The driver does not implement native chip-selects but uses the aribtrary GPIO-chip-selects provided by the spi-chipselect. Note that this driver relies on the fact that the clock is implemented by the clk-bcm2835-aux driver, which enables/disables the HW block when requesting/releasing the clock. Signed-off-by: Martin Sperl <kernel@martin.sperl.org> Acked-by: Eric Anholt <eric@anholt.net> Signed-off-by: Mark Brown <broonie@kernel.org>
This commit is contained in:
		
							parent
							
								
									541cf5de02
								
							
						
					
					
						commit
						1ea29b39f4
					
				
					 3 changed files with 505 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -88,6 +88,17 @@ config SPI_BCM2835
 | 
			
		|||
	  is for the regular SPI controller. Slave mode operation is not also
 | 
			
		||||
	  not supported.
 | 
			
		||||
 | 
			
		||||
config SPI_BCM2835AUX
 | 
			
		||||
	tristate "BCM2835 SPI auxiliary controller"
 | 
			
		||||
	depends on ARCH_BCM2835 || COMPILE_TEST
 | 
			
		||||
	depends on GPIOLIB
 | 
			
		||||
	help
 | 
			
		||||
	  This selects a driver for the Broadcom BCM2835 SPI aux master.
 | 
			
		||||
 | 
			
		||||
	  The BCM2835 contains two types of SPI master controller; the
 | 
			
		||||
	  "universal SPI master", and the regular SPI controller.
 | 
			
		||||
	  This driver is for the universal/auxiliary SPI controller.
 | 
			
		||||
 | 
			
		||||
config SPI_BFIN5XX
 | 
			
		||||
	tristate "SPI controller driver for ADI Blackfin5xx"
 | 
			
		||||
	depends on BLACKFIN && !BF60x
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ obj-$(CONFIG_SPI_ATMEL)			+= spi-atmel.o
 | 
			
		|||
obj-$(CONFIG_SPI_ATH79)			+= spi-ath79.o
 | 
			
		||||
obj-$(CONFIG_SPI_AU1550)		+= spi-au1550.o
 | 
			
		||||
obj-$(CONFIG_SPI_BCM2835)		+= spi-bcm2835.o
 | 
			
		||||
obj-$(CONFIG_SPI_BCM2835AUX)		+= spi-bcm2835aux.o
 | 
			
		||||
obj-$(CONFIG_SPI_BCM53XX)		+= spi-bcm53xx.o
 | 
			
		||||
obj-$(CONFIG_SPI_BCM63XX)		+= spi-bcm63xx.o
 | 
			
		||||
obj-$(CONFIG_SPI_BCM63XX_HSSPI)		+= spi-bcm63xx-hsspi.o
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										493
									
								
								drivers/spi/spi-bcm2835aux.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										493
									
								
								drivers/spi/spi-bcm2835aux.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,493 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Driver for Broadcom BCM2835 auxiliary SPI Controllers
 | 
			
		||||
 *
 | 
			
		||||
 * the driver does not rely on the native chipselects at all
 | 
			
		||||
 * but only uses the gpio type chipselects
 | 
			
		||||
 *
 | 
			
		||||
 * Based on: spi-bcm2835.c
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (C) 2015 Martin Sperl
 | 
			
		||||
 *
 | 
			
		||||
 * This program is free software; you can redistribute it and/or modify
 | 
			
		||||
 * it under the terms of the GNU General Public License as published by
 | 
			
		||||
 * the Free Software Foundation; either version 2 of the License, or
 | 
			
		||||
 * (at your option) any later version.
 | 
			
		||||
 *
 | 
			
		||||
 * This program is distributed in the hope that it will be useful,
 | 
			
		||||
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
 * GNU General Public License for more details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
#include <linux/clk.h>
 | 
			
		||||
#include <linux/completion.h>
 | 
			
		||||
#include <linux/delay.h>
 | 
			
		||||
#include <linux/err.h>
 | 
			
		||||
#include <linux/interrupt.h>
 | 
			
		||||
#include <linux/io.h>
 | 
			
		||||
#include <linux/kernel.h>
 | 
			
		||||
#include <linux/module.h>
 | 
			
		||||
#include <linux/of.h>
 | 
			
		||||
#include <linux/of_address.h>
 | 
			
		||||
#include <linux/of_device.h>
 | 
			
		||||
#include <linux/of_gpio.h>
 | 
			
		||||
#include <linux/of_irq.h>
 | 
			
		||||
#include <linux/regmap.h>
 | 
			
		||||
#include <linux/spi/spi.h>
 | 
			
		||||
#include <linux/spinlock.h>
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * spi register defines
 | 
			
		||||
 *
 | 
			
		||||
 * note there is garbage in the "official" documentation,
 | 
			
		||||
 * so some data is taken from the file:
 | 
			
		||||
 *   brcm_usrlib/dag/vmcsx/vcinclude/bcm2708_chip/aux_io.h
 | 
			
		||||
 * inside of:
 | 
			
		||||
 *   http://www.broadcom.com/docs/support/videocore/Brcm_Android_ICS_Graphics_Stack.tar.gz
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* SPI register offsets */
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0	0x00
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL1	0x04
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT	0x08
 | 
			
		||||
#define BCM2835_AUX_SPI_PEEK	0x0C
 | 
			
		||||
#define BCM2835_AUX_SPI_IO	0x20
 | 
			
		||||
#define BCM2835_AUX_SPI_TXHOLD	0x30
 | 
			
		||||
 | 
			
		||||
/* Bitfields in CNTL0 */
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_SPEED	0xFFF00000
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_SPEED_MAX	0xFFF
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_SPEED_SHIFT	20
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_CS	0x000E0000
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_POSTINPUT	0x00010000
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_VAR_CS	0x00008000
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_VAR_WIDTH	0x00004000
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_DOUTHOLD	0x00003000
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_ENABLE	0x00000800
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_CPHA_IN	0x00000400
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_CLEARFIFO	0x00000200
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_CPHA_OUT	0x00000100
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_CPOL	0x00000080
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_MSBF_OUT	0x00000040
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL0_SHIFTLEN	0x0000003F
 | 
			
		||||
 | 
			
		||||
/* Bitfields in CNTL1 */
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL1_CSHIGH	0x00000700
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL1_IDLE	0x00000080
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL1_TXEMPTY	0x00000040
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL1_MSBF_IN	0x00000002
 | 
			
		||||
#define BCM2835_AUX_SPI_CNTL1_KEEP_IN	0x00000001
 | 
			
		||||
 | 
			
		||||
/* Bitfields in STAT */
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT_TX_LVL	0xFF000000
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT_RX_LVL	0x00FF0000
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT_TX_FULL	0x00000400
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT_TX_EMPTY	0x00000200
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT_RX_FULL	0x00000100
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT_RX_EMPTY	0x00000080
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT_BUSY	0x00000040
 | 
			
		||||
#define BCM2835_AUX_SPI_STAT_BITCOUNT	0x0000003F
 | 
			
		||||
 | 
			
		||||
/* timeout values */
 | 
			
		||||
#define BCM2835_AUX_SPI_POLLING_LIMIT_US	30
 | 
			
		||||
#define BCM2835_AUX_SPI_POLLING_JIFFIES		2
 | 
			
		||||
 | 
			
		||||
#define BCM2835_AUX_SPI_MODE_BITS (SPI_CPOL | SPI_CPHA | SPI_CS_HIGH \
 | 
			
		||||
				  | SPI_NO_CS)
 | 
			
		||||
 | 
			
		||||
struct bcm2835aux_spi {
 | 
			
		||||
	void __iomem *regs;
 | 
			
		||||
	struct clk *clk;
 | 
			
		||||
	int irq;
 | 
			
		||||
	u32 cntl[2];
 | 
			
		||||
	const u8 *tx_buf;
 | 
			
		||||
	u8 *rx_buf;
 | 
			
		||||
	int tx_len;
 | 
			
		||||
	int rx_len;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static inline u32 bcm2835aux_rd(struct bcm2835aux_spi *bs, unsigned reg)
 | 
			
		||||
{
 | 
			
		||||
	return readl(bs->regs + reg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline void bcm2835aux_wr(struct bcm2835aux_spi *bs, unsigned reg,
 | 
			
		||||
				 u32 val)
 | 
			
		||||
{
 | 
			
		||||
	writel(val, bs->regs + reg);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline void bcm2835aux_rd_fifo(struct bcm2835aux_spi *bs)
 | 
			
		||||
{
 | 
			
		||||
	u32 data;
 | 
			
		||||
	int i;
 | 
			
		||||
	int count = min(bs->rx_len, 3);
 | 
			
		||||
 | 
			
		||||
	data = bcm2835aux_rd(bs, BCM2835_AUX_SPI_IO);
 | 
			
		||||
	if (bs->rx_buf) {
 | 
			
		||||
		for (i = 0; i < count; i++)
 | 
			
		||||
			*bs->rx_buf++ = (data >> (8 * (2 - i))) & 0xff;
 | 
			
		||||
	}
 | 
			
		||||
	bs->rx_len -= count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline void bcm2835aux_wr_fifo(struct bcm2835aux_spi *bs)
 | 
			
		||||
{
 | 
			
		||||
	u32 data;
 | 
			
		||||
	u8 byte;
 | 
			
		||||
	int count;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	/* gather up to 3 bytes to write to the FIFO */
 | 
			
		||||
	count = min(bs->tx_len, 3);
 | 
			
		||||
	data = 0;
 | 
			
		||||
	for (i = 0; i < count; i++) {
 | 
			
		||||
		byte = bs->tx_buf ? *bs->tx_buf++ : 0;
 | 
			
		||||
		data |= byte << (8 * (2 - i));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* and set the variable bit-length */
 | 
			
		||||
	data |= (count * 8) << 24;
 | 
			
		||||
 | 
			
		||||
	/* and decrement length */
 | 
			
		||||
	bs->tx_len -= count;
 | 
			
		||||
 | 
			
		||||
	/* write to the correct TX-register */
 | 
			
		||||
	if (bs->tx_len)
 | 
			
		||||
		bcm2835aux_wr(bs, BCM2835_AUX_SPI_TXHOLD, data);
 | 
			
		||||
	else
 | 
			
		||||
		bcm2835aux_wr(bs, BCM2835_AUX_SPI_IO, data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void bcm2835aux_spi_reset_hw(struct bcm2835aux_spi *bs)
 | 
			
		||||
{
 | 
			
		||||
	/* disable spi clearing fifo and interrupts */
 | 
			
		||||
	bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, 0);
 | 
			
		||||
	bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL0,
 | 
			
		||||
		      BCM2835_AUX_SPI_CNTL0_CLEARFIFO);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static irqreturn_t bcm2835aux_spi_interrupt(int irq, void *dev_id)
 | 
			
		||||
{
 | 
			
		||||
	struct spi_master *master = dev_id;
 | 
			
		||||
	struct bcm2835aux_spi *bs = spi_master_get_devdata(master);
 | 
			
		||||
	irqreturn_t ret = IRQ_NONE;
 | 
			
		||||
 | 
			
		||||
	/* check if we have data to read */
 | 
			
		||||
	while (bs->rx_len &&
 | 
			
		||||
	       (!(bcm2835aux_rd(bs, BCM2835_AUX_SPI_STAT) &
 | 
			
		||||
		  BCM2835_AUX_SPI_STAT_RX_EMPTY))) {
 | 
			
		||||
		bcm2835aux_rd_fifo(bs);
 | 
			
		||||
		ret = IRQ_HANDLED;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* check if we have data to write */
 | 
			
		||||
	while (bs->tx_len &&
 | 
			
		||||
	       (!(bcm2835aux_rd(bs, BCM2835_AUX_SPI_STAT) &
 | 
			
		||||
		  BCM2835_AUX_SPI_STAT_TX_FULL))) {
 | 
			
		||||
		bcm2835aux_wr_fifo(bs);
 | 
			
		||||
		ret = IRQ_HANDLED;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* and check if we have reached "done" */
 | 
			
		||||
	while (bs->rx_len &&
 | 
			
		||||
	       (!(bcm2835aux_rd(bs, BCM2835_AUX_SPI_STAT) &
 | 
			
		||||
		  BCM2835_AUX_SPI_STAT_BUSY))) {
 | 
			
		||||
		bcm2835aux_rd_fifo(bs);
 | 
			
		||||
		ret = IRQ_HANDLED;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* and if rx_len is 0 then wake up completion and disable spi */
 | 
			
		||||
	if (!bs->rx_len) {
 | 
			
		||||
		bcm2835aux_spi_reset_hw(bs);
 | 
			
		||||
		complete(&master->xfer_completion);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* and return */
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int __bcm2835aux_spi_transfer_one_irq(struct spi_master *master,
 | 
			
		||||
					     struct spi_device *spi,
 | 
			
		||||
					     struct spi_transfer *tfr)
 | 
			
		||||
{
 | 
			
		||||
	struct bcm2835aux_spi *bs = spi_master_get_devdata(master);
 | 
			
		||||
 | 
			
		||||
	/* enable interrupts */
 | 
			
		||||
	bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1] |
 | 
			
		||||
		BCM2835_AUX_SPI_CNTL1_TXEMPTY |
 | 
			
		||||
		BCM2835_AUX_SPI_CNTL1_IDLE);
 | 
			
		||||
 | 
			
		||||
	/* and wait for finish... */
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int bcm2835aux_spi_transfer_one_irq(struct spi_master *master,
 | 
			
		||||
					   struct spi_device *spi,
 | 
			
		||||
					   struct spi_transfer *tfr)
 | 
			
		||||
{
 | 
			
		||||
	struct bcm2835aux_spi *bs = spi_master_get_devdata(master);
 | 
			
		||||
 | 
			
		||||
	/* fill in registers and fifos before enabling interrupts */
 | 
			
		||||
	bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1]);
 | 
			
		||||
	bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL0, bs->cntl[0]);
 | 
			
		||||
 | 
			
		||||
	/* fill in tx fifo with data before enabling interrupts */
 | 
			
		||||
	while ((bs->tx_len) &&
 | 
			
		||||
	       (!(bcm2835aux_rd(bs, BCM2835_AUX_SPI_STAT) &
 | 
			
		||||
		  BCM2835_AUX_SPI_STAT_TX_FULL))) {
 | 
			
		||||
		bcm2835aux_wr_fifo(bs);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* now run the interrupt mode */
 | 
			
		||||
	return __bcm2835aux_spi_transfer_one_irq(master, spi, tfr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int bcm2835aux_spi_transfer_one_poll(struct spi_master *master,
 | 
			
		||||
					    struct spi_device *spi,
 | 
			
		||||
					    struct spi_transfer *tfr,
 | 
			
		||||
					    unsigned long xfer_time_us)
 | 
			
		||||
{
 | 
			
		||||
	struct bcm2835aux_spi *bs = spi_master_get_devdata(master);
 | 
			
		||||
	unsigned long timeout;
 | 
			
		||||
	u32 stat;
 | 
			
		||||
 | 
			
		||||
	/* configure spi */
 | 
			
		||||
	bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1]);
 | 
			
		||||
	bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL0, bs->cntl[0]);
 | 
			
		||||
 | 
			
		||||
	/* set the timeout */
 | 
			
		||||
	timeout = jiffies + BCM2835_AUX_SPI_POLLING_JIFFIES;
 | 
			
		||||
 | 
			
		||||
	/* loop until finished the transfer */
 | 
			
		||||
	while (bs->rx_len) {
 | 
			
		||||
		/* read status */
 | 
			
		||||
		stat = bcm2835aux_rd(bs, BCM2835_AUX_SPI_STAT);
 | 
			
		||||
 | 
			
		||||
		/* fill in tx fifo with remaining data */
 | 
			
		||||
		if ((bs->tx_len) && (!(stat & BCM2835_AUX_SPI_STAT_TX_FULL))) {
 | 
			
		||||
			bcm2835aux_wr_fifo(bs);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* read data from fifo for both cases */
 | 
			
		||||
		if (!(stat & BCM2835_AUX_SPI_STAT_RX_EMPTY)) {
 | 
			
		||||
			bcm2835aux_rd_fifo(bs);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
		if (!(stat & BCM2835_AUX_SPI_STAT_BUSY)) {
 | 
			
		||||
			bcm2835aux_rd_fifo(bs);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/* there is still data pending to read check the timeout */
 | 
			
		||||
		if (bs->rx_len && time_after(jiffies, timeout)) {
 | 
			
		||||
			dev_dbg_ratelimited(&spi->dev,
 | 
			
		||||
					    "timeout period reached: jiffies: %lu remaining tx/rx: %d/%d - falling back to interrupt mode\n",
 | 
			
		||||
					    jiffies - timeout,
 | 
			
		||||
					    bs->tx_len, bs->rx_len);
 | 
			
		||||
			/* forward to interrupt handler */
 | 
			
		||||
			return __bcm2835aux_spi_transfer_one_irq(master,
 | 
			
		||||
							       spi, tfr);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Transfer complete - reset SPI HW */
 | 
			
		||||
	bcm2835aux_spi_reset_hw(bs);
 | 
			
		||||
 | 
			
		||||
	/* and return without waiting for completion */
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int bcm2835aux_spi_transfer_one(struct spi_master *master,
 | 
			
		||||
				       struct spi_device *spi,
 | 
			
		||||
				       struct spi_transfer *tfr)
 | 
			
		||||
{
 | 
			
		||||
	struct bcm2835aux_spi *bs = spi_master_get_devdata(master);
 | 
			
		||||
	unsigned long spi_hz, clk_hz, speed;
 | 
			
		||||
	unsigned long spi_used_hz, xfer_time_us;
 | 
			
		||||
 | 
			
		||||
	/* calculate the registers to handle
 | 
			
		||||
	 *
 | 
			
		||||
	 * note that we use the variable data mode, which
 | 
			
		||||
	 * is not optimal for longer transfers as we waste registers
 | 
			
		||||
	 * resulting (potentially) in more interrupts when transferring
 | 
			
		||||
	 * more than 12 bytes
 | 
			
		||||
	 */
 | 
			
		||||
	bs->cntl[0] = BCM2835_AUX_SPI_CNTL0_ENABLE |
 | 
			
		||||
		      BCM2835_AUX_SPI_CNTL0_VAR_WIDTH |
 | 
			
		||||
		      BCM2835_AUX_SPI_CNTL0_MSBF_OUT;
 | 
			
		||||
	bs->cntl[1] = BCM2835_AUX_SPI_CNTL1_MSBF_IN;
 | 
			
		||||
 | 
			
		||||
	/* set clock */
 | 
			
		||||
	spi_hz = tfr->speed_hz;
 | 
			
		||||
	clk_hz = clk_get_rate(bs->clk);
 | 
			
		||||
 | 
			
		||||
	if (spi_hz >= clk_hz / 2) {
 | 
			
		||||
		speed = 0;
 | 
			
		||||
	} else if (spi_hz) {
 | 
			
		||||
		speed = DIV_ROUND_UP(clk_hz, 2 * spi_hz) - 1;
 | 
			
		||||
		if (speed >  BCM2835_AUX_SPI_CNTL0_SPEED_MAX)
 | 
			
		||||
			speed = BCM2835_AUX_SPI_CNTL0_SPEED_MAX;
 | 
			
		||||
	} else { /* the slowest we can go */
 | 
			
		||||
		speed = BCM2835_AUX_SPI_CNTL0_SPEED_MAX;
 | 
			
		||||
	}
 | 
			
		||||
	bs->cntl[0] |= speed << BCM2835_AUX_SPI_CNTL0_SPEED_SHIFT;
 | 
			
		||||
 | 
			
		||||
	spi_used_hz = clk_hz / (2 * (speed + 1));
 | 
			
		||||
 | 
			
		||||
	/* handle all the modes */
 | 
			
		||||
	if (spi->mode & SPI_CPOL)
 | 
			
		||||
		bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_CPOL;
 | 
			
		||||
	if (spi->mode & SPI_CPHA)
 | 
			
		||||
		bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_CPHA_OUT |
 | 
			
		||||
			       BCM2835_AUX_SPI_CNTL0_CPHA_IN;
 | 
			
		||||
 | 
			
		||||
	/* set transmit buffers and length */
 | 
			
		||||
	bs->tx_buf = tfr->tx_buf;
 | 
			
		||||
	bs->rx_buf = tfr->rx_buf;
 | 
			
		||||
	bs->tx_len = tfr->len;
 | 
			
		||||
	bs->rx_len = tfr->len;
 | 
			
		||||
 | 
			
		||||
	/* calculate the estimated time in us the transfer runs */
 | 
			
		||||
	xfer_time_us = tfr->len
 | 
			
		||||
		* 9 /* clocks/byte - SPI-HW waits 1 clock after each byte */
 | 
			
		||||
		* 1000000 / spi_used_hz;
 | 
			
		||||
 | 
			
		||||
	/* run in polling mode for short transfers */
 | 
			
		||||
	if (xfer_time_us < BCM2835_AUX_SPI_POLLING_LIMIT_US)
 | 
			
		||||
		return bcm2835aux_spi_transfer_one_poll(master, spi, tfr,
 | 
			
		||||
							xfer_time_us);
 | 
			
		||||
 | 
			
		||||
	/* run in interrupt mode for all others */
 | 
			
		||||
	return bcm2835aux_spi_transfer_one_irq(master, spi, tfr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void bcm2835aux_spi_handle_err(struct spi_master *master,
 | 
			
		||||
				      struct spi_message *msg)
 | 
			
		||||
{
 | 
			
		||||
	struct bcm2835aux_spi *bs = spi_master_get_devdata(master);
 | 
			
		||||
 | 
			
		||||
	bcm2835aux_spi_reset_hw(bs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int bcm2835aux_spi_probe(struct platform_device *pdev)
 | 
			
		||||
{
 | 
			
		||||
	struct spi_master *master;
 | 
			
		||||
	struct bcm2835aux_spi *bs;
 | 
			
		||||
	struct resource *res;
 | 
			
		||||
	unsigned long clk_hz;
 | 
			
		||||
	int err;
 | 
			
		||||
 | 
			
		||||
	master = spi_alloc_master(&pdev->dev, sizeof(*bs));
 | 
			
		||||
	if (!master) {
 | 
			
		||||
		dev_err(&pdev->dev, "spi_alloc_master() failed\n");
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	platform_set_drvdata(pdev, master);
 | 
			
		||||
	master->mode_bits = BCM2835_AUX_SPI_MODE_BITS;
 | 
			
		||||
	master->bits_per_word_mask = SPI_BPW_MASK(8);
 | 
			
		||||
	master->num_chipselect = -1;
 | 
			
		||||
	master->transfer_one = bcm2835aux_spi_transfer_one;
 | 
			
		||||
	master->handle_err = bcm2835aux_spi_handle_err;
 | 
			
		||||
	master->dev.of_node = pdev->dev.of_node;
 | 
			
		||||
 | 
			
		||||
	bs = spi_master_get_devdata(master);
 | 
			
		||||
 | 
			
		||||
	/* the main area */
 | 
			
		||||
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | 
			
		||||
	bs->regs = devm_ioremap_resource(&pdev->dev, res);
 | 
			
		||||
	if (IS_ERR(bs->regs)) {
 | 
			
		||||
		err = PTR_ERR(bs->regs);
 | 
			
		||||
		goto out_master_put;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bs->clk = devm_clk_get(&pdev->dev, NULL);
 | 
			
		||||
	if ((!bs->clk) || (IS_ERR(bs->clk))) {
 | 
			
		||||
		err = PTR_ERR(bs->clk);
 | 
			
		||||
		dev_err(&pdev->dev, "could not get clk: %d\n", err);
 | 
			
		||||
		goto out_master_put;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bs->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
 | 
			
		||||
	if (bs->irq <= 0) {
 | 
			
		||||
		dev_err(&pdev->dev, "could not get IRQ: %d\n", bs->irq);
 | 
			
		||||
		err = bs->irq ? bs->irq : -ENODEV;
 | 
			
		||||
		goto out_master_put;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* this also enables the HW block */
 | 
			
		||||
	err = clk_prepare_enable(bs->clk);
 | 
			
		||||
	if (err) {
 | 
			
		||||
		dev_err(&pdev->dev, "could not prepare clock: %d\n", err);
 | 
			
		||||
		goto out_master_put;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* just checking if the clock returns a sane value */
 | 
			
		||||
	clk_hz = clk_get_rate(bs->clk);
 | 
			
		||||
	if (!clk_hz) {
 | 
			
		||||
		dev_err(&pdev->dev, "clock returns 0 Hz\n");
 | 
			
		||||
		err = -ENODEV;
 | 
			
		||||
		goto out_clk_disable;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = devm_request_irq(&pdev->dev, bs->irq,
 | 
			
		||||
			       bcm2835aux_spi_interrupt,
 | 
			
		||||
			       IRQF_SHARED,
 | 
			
		||||
			       dev_name(&pdev->dev), master);
 | 
			
		||||
	if (err) {
 | 
			
		||||
		dev_err(&pdev->dev, "could not request IRQ: %d\n", err);
 | 
			
		||||
		goto out_clk_disable;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* reset SPI-HW block */
 | 
			
		||||
	bcm2835aux_spi_reset_hw(bs);
 | 
			
		||||
 | 
			
		||||
	err = devm_spi_register_master(&pdev->dev, master);
 | 
			
		||||
	if (err) {
 | 
			
		||||
		dev_err(&pdev->dev, "could not register SPI master: %d\n", err);
 | 
			
		||||
		goto out_clk_disable;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
 | 
			
		||||
out_clk_disable:
 | 
			
		||||
	clk_disable_unprepare(bs->clk);
 | 
			
		||||
out_master_put:
 | 
			
		||||
	spi_master_put(master);
 | 
			
		||||
	return err;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int bcm2835aux_spi_remove(struct platform_device *pdev)
 | 
			
		||||
{
 | 
			
		||||
	struct spi_master *master = platform_get_drvdata(pdev);
 | 
			
		||||
	struct bcm2835aux_spi *bs = spi_master_get_devdata(master);
 | 
			
		||||
 | 
			
		||||
	bcm2835aux_spi_reset_hw(bs);
 | 
			
		||||
 | 
			
		||||
	/* disable the HW block by releasing the clock */
 | 
			
		||||
	clk_disable_unprepare(bs->clk);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const struct of_device_id bcm2835aux_spi_match[] = {
 | 
			
		||||
	{ .compatible = "brcm,bcm2835-aux-spi", },
 | 
			
		||||
	{}
 | 
			
		||||
};
 | 
			
		||||
MODULE_DEVICE_TABLE(of, bcm2835aux_spi_match);
 | 
			
		||||
 | 
			
		||||
static struct platform_driver bcm2835aux_spi_driver = {
 | 
			
		||||
	.driver		= {
 | 
			
		||||
		.name		= "spi-bcm2835aux",
 | 
			
		||||
		.of_match_table	= bcm2835aux_spi_match,
 | 
			
		||||
	},
 | 
			
		||||
	.probe		= bcm2835aux_spi_probe,
 | 
			
		||||
	.remove		= bcm2835aux_spi_remove,
 | 
			
		||||
};
 | 
			
		||||
module_platform_driver(bcm2835aux_spi_driver);
 | 
			
		||||
 | 
			
		||||
MODULE_DESCRIPTION("SPI controller driver for Broadcom BCM2835 aux");
 | 
			
		||||
MODULE_AUTHOR("Martin Sperl <kernel@martin.sperl.org>");
 | 
			
		||||
MODULE_LICENSE("GPL v2");
 | 
			
		||||
		Loading…
	
		Reference in a new issue