From 0beb0a6da8896da14287b1caa77596057690901a Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Tue, 15 Sep 2015 16:26:18 +0300 Subject: spi: au1550: Simplify au1550_spi_setupxfer() SPI core validates both bits_per_word and speed_hz transfer parameters and defaults to spi->bits_per_word and spi->max_speed_hz in case these per transfer parameters are not set. This can simplify a little the au1550_spi_setupxfer() as there is need to check only for valid "struct spi_transfer" pointer. Signed-off-by: Jarkko Nikula Signed-off-by: Mark Brown --- drivers/spi/spi-au1550.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-au1550.c b/drivers/spi/spi-au1550.c index f45e085..afd239d 100644 --- a/drivers/spi/spi-au1550.c +++ b/drivers/spi/spi-au1550.c @@ -233,13 +233,12 @@ static int au1550_spi_setupxfer(struct spi_device *spi, struct spi_transfer *t) unsigned bpw, hz; u32 cfg, stat; - bpw = spi->bits_per_word; - hz = spi->max_speed_hz; if (t) { - if (t->bits_per_word) - bpw = t->bits_per_word; - if (t->speed_hz) - hz = t->speed_hz; + bpw = t->bits_per_word; + hz = t->speed_hz; + } else { + bpw = spi->bits_per_word; + hz = spi->max_speed_hz; } if (!hz) -- cgit v1.1 From e8646580cc9a6ae24ab3d05f90e71334f50cdedf Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Fri, 25 Sep 2015 09:03:01 +0300 Subject: spi: atmel: Remove needless bits_per_word and speed_hz tests SPI core validates both bits_per_word and speed_hz transfer parameters and defaults to spi->bits_per_word and spi->max_speed_hz in case these per transfer parameters are not set. This makes possible to remove two if statements and remove one code block that is never executed. Signed-off-by: Jarkko Nikula Acked-by: Nicolas Ferre Signed-off-by: Mark Brown --- drivers/spi/spi-atmel.c | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-atmel.c b/drivers/spi/spi-atmel.c index bf9ed38..1cc1f1e 100644 --- a/drivers/spi/spi-atmel.c +++ b/drivers/spi/spi-atmel.c @@ -871,14 +871,7 @@ static int atmel_spi_set_xfer_speed(struct atmel_spi *as, * Calculate the lowest divider that satisfies the * constraint, assuming div32/fdiv/mbz == 0. */ - if (xfer->speed_hz) - scbr = DIV_ROUND_UP(bus_hz, xfer->speed_hz); - else - /* - * This can happend if max_speed is null. - * In this case, we set the lowest possible speed - */ - scbr = 0xff; + scbr = DIV_ROUND_UP(bus_hz, xfer->speed_hz); /* * If the resulting divider doesn't fit into the @@ -1300,14 +1293,12 @@ static int atmel_spi_one_transfer(struct spi_master *master, return -EINVAL; } - if (xfer->bits_per_word) { - asd = spi->controller_state; - bits = (asd->csr >> 4) & 0xf; - if (bits != xfer->bits_per_word - 8) { - dev_dbg(&spi->dev, + asd = spi->controller_state; + bits = (asd->csr >> 4) & 0xf; + if (bits != xfer->bits_per_word - 8) { + dev_dbg(&spi->dev, "you can't yet change bits_per_word in transfers\n"); - return -ENOPROTOOPT; - } + return -ENOPROTOOPT; } /* -- cgit v1.1 From b7a2a1c0b67283442a06fcc679cba0683914f794 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Sun, 27 Sep 2015 18:47:35 +0200 Subject: spi: ath79: simplify iomem resource mapping Simplify the code by switching from devm_ioremap to devm_ioremap_resource. Checking the result of platform_get_resource is not needed as devm_ioremap_resource checks the provided resource argument for NULL. Signed-off-by: Heiner Kallweit Signed-off-by: Mark Brown --- drivers/spi/spi-ath79.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-ath79.c b/drivers/spi/spi-ath79.c index bf1f9b3..6165bf2 100644 --- a/drivers/spi/spi-ath79.c +++ b/drivers/spi/spi-ath79.c @@ -240,14 +240,9 @@ static int ath79_spi_probe(struct platform_device *pdev) sp->bitbang.flags = SPI_CS_HIGH; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - ret = -ENOENT; - goto err_put_master; - } - - sp->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); - if (!sp->base) { - ret = -ENXIO; + sp->base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(sp->base)) { + ret = PTR_ERR(sp->base); goto err_put_master; } -- cgit v1.1 From 1ea29b39f4c812ece2f936065a0a3d6fe44a263e Mon Sep 17 00:00:00 2001 From: Martin Sperl Date: Fri, 11 Sep 2015 11:22:04 +0000 Subject: 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 Acked-by: Eric Anholt Signed-off-by: Mark Brown --- drivers/spi/Kconfig | 11 + drivers/spi/Makefile | 1 + drivers/spi/spi-bcm2835aux.c | 493 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 505 insertions(+) create mode 100644 drivers/spi/spi-bcm2835aux.c (limited to 'drivers/spi') diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 4887f31..26b8605 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -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 diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 6a7f6f9..31fb7fb 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -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 diff --git a/drivers/spi/spi-bcm2835aux.c b/drivers/spi/spi-bcm2835aux.c new file mode 100644 index 0000000..f92b4a6 --- /dev/null +++ b/drivers/spi/spi-bcm2835aux.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 "); +MODULE_LICENSE("GPL v2"); -- cgit v1.1 From ddf0e1c20d8b72c409e4c954002069445babc762 Mon Sep 17 00:00:00 2001 From: Martin Sperl Date: Thu, 15 Oct 2015 10:09:11 +0000 Subject: spi: bcm2835: change initialization order and switch to platform_get_irq Change the initialization order of the HW so that the interrupt is only requested after the HW is initialized Also the use of irq_of_parse_and_map is replaced by platform_get_irq. Signed-off-by: Martin Sperl Signed-off-by: Mark Brown --- drivers/spi/spi-bcm2835.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-bcm2835.c b/drivers/spi/spi-bcm2835.c index e7874a6..370822e 100644 --- a/drivers/spi/spi-bcm2835.c +++ b/drivers/spi/spi-bcm2835.c @@ -777,7 +777,7 @@ static int bcm2835_spi_probe(struct platform_device *pdev) goto out_master_put; } - bs->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + bs->irq = platform_get_irq(pdev, 0); if (bs->irq <= 0) { dev_err(&pdev->dev, "could not get IRQ: %d\n", bs->irq); err = bs->irq ? bs->irq : -ENODEV; @@ -786,6 +786,12 @@ static int bcm2835_spi_probe(struct platform_device *pdev) clk_prepare_enable(bs->clk); + bcm2835_dma_init(master, &pdev->dev); + + /* initialise the hardware with the default polarities */ + bcm2835_wr(bs, BCM2835_SPI_CS, + BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX); + err = devm_request_irq(&pdev->dev, bs->irq, bcm2835_spi_interrupt, 0, dev_name(&pdev->dev), master); if (err) { @@ -793,12 +799,6 @@ static int bcm2835_spi_probe(struct platform_device *pdev) goto out_clk_disable; } - bcm2835_dma_init(master, &pdev->dev); - - /* initialise the hardware with the default polarities */ - bcm2835_wr(bs, BCM2835_SPI_CS, - BCM2835_SPI_CS_CLEAR_RX | BCM2835_SPI_CS_CLEAR_TX); - err = devm_spi_register_master(&pdev->dev, master); if (err) { dev_err(&pdev->dev, "could not register SPI master: %d\n", err); -- cgit v1.1 From 72aac02b3730fa0e2e1ccab57712a94400344f8a Mon Sep 17 00:00:00 2001 From: Martin Sperl Date: Fri, 16 Oct 2015 14:17:19 +0000 Subject: spi: bcm2835aux: fixed bad data on longer transfers There are strange issues with the auxiliary spi device that result in "lost" data in the RX path if the fifo is filled by too much (even though the status register is checked if new data can get filled in). This has been observed primarily for the interrupt case. Polling works fine, probably because the RX fifo is pulled immediately when in the tight polling loop. For that reason we have to limit the pending bytes to less than 15 when filling the fifo in interrupt mode. There also was an issue returning the "wrong" last 1/2 bytes of a transfer when the transfer is not a multiple of 3 bytes. (this impacted polling and interrupt modes) Also fixed an overflow in the estimation of the transfer time used to decide if we run in interrupt or polling mode (found with the spi-bcm2835.c driver originally). Reported-by: Georgii Staroselskii Signed-off-by: Martin Sperl Signed-off-by: Mark Brown --- drivers/spi/spi-bcm2835aux.c | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-bcm2835aux.c b/drivers/spi/spi-bcm2835aux.c index f92b4a6..05d2d6e 100644 --- a/drivers/spi/spi-bcm2835aux.c +++ b/drivers/spi/spi-bcm2835aux.c @@ -104,6 +104,7 @@ struct bcm2835aux_spi { u8 *rx_buf; int tx_len; int rx_len; + int pending; }; static inline u32 bcm2835aux_rd(struct bcm2835aux_spi *bs, unsigned reg) @@ -120,15 +121,27 @@ static inline void bcm2835aux_wr(struct bcm2835aux_spi *bs, unsigned 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; + switch (count) { + case 4: + *bs->rx_buf++ = (data >> 24) & 0xff; + /* fallthrough */ + case 3: + *bs->rx_buf++ = (data >> 16) & 0xff; + /* fallthrough */ + case 2: + *bs->rx_buf++ = (data >> 8) & 0xff; + /* fallthrough */ + case 1: + *bs->rx_buf++ = (data >> 0) & 0xff; + /* fallthrough - no default */ + } } bs->rx_len -= count; + bs->pending -= count; } static inline void bcm2835aux_wr_fifo(struct bcm2835aux_spi *bs) @@ -151,6 +164,7 @@ static inline void bcm2835aux_wr_fifo(struct bcm2835aux_spi *bs) /* and decrement length */ bs->tx_len -= count; + bs->pending += count; /* write to the correct TX-register */ if (bs->tx_len) @@ -183,6 +197,7 @@ static irqreturn_t bcm2835aux_spi_interrupt(int irq, void *dev_id) /* check if we have data to write */ while (bs->tx_len && + (bs->pending < 12) && (!(bcm2835aux_rd(bs, BCM2835_AUX_SPI_STAT) & BCM2835_AUX_SPI_STAT_TX_FULL))) { bcm2835aux_wr_fifo(bs); @@ -234,6 +249,7 @@ static int bcm2835aux_spi_transfer_one_irq(struct spi_master *master, /* fill in tx fifo with data before enabling interrupts */ while ((bs->tx_len) && + (bs->pending < 12) && (!(bcm2835aux_rd(bs, BCM2835_AUX_SPI_STAT) & BCM2835_AUX_SPI_STAT_TX_FULL))) { bcm2835aux_wr_fifo(bs); @@ -245,8 +261,7 @@ static int bcm2835aux_spi_transfer_one_irq(struct spi_master *master, 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 spi_transfer *tfr) { struct bcm2835aux_spi *bs = spi_master_get_devdata(master); unsigned long timeout; @@ -305,7 +320,8 @@ static int bcm2835aux_spi_transfer_one(struct spi_master *master, { struct bcm2835aux_spi *bs = spi_master_get_devdata(master); unsigned long spi_hz, clk_hz, speed; - unsigned long spi_used_hz, xfer_time_us; + unsigned long spi_used_hz; + unsigned long long xfer_time_us; /* calculate the registers to handle * @@ -348,16 +364,19 @@ static int bcm2835aux_spi_transfer_one(struct spi_master *master, bs->rx_buf = tfr->rx_buf; bs->tx_len = tfr->len; bs->rx_len = tfr->len; + bs->pending = 0; - /* 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; + /* calculate the estimated time in us the transfer runs + * note that there are are 2 idle clocks after each + * chunk getting transferred - in our case the chunk size + * is 3 bytes, so we approximate this by 9 bits/byte + */ + xfer_time_us = tfr->len * 9 * 1000000; + do_div(xfer_time_us, 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); + return bcm2835aux_spi_transfer_one_poll(master, spi, tfr); /* run in interrupt mode for all others */ return bcm2835aux_spi_transfer_one_irq(master, spi, tfr); -- cgit v1.1 From 07bce09e11f5007635d7f9e6bc55679dd6ed18bd Mon Sep 17 00:00:00 2001 From: Martin Sperl Date: Thu, 15 Oct 2015 10:10:20 +0000 Subject: spi: bcm2835aux: change initialization order and switch to platform_get_irq Change the initialization order of the HW so that the interrupt is only requested after the HW is initialized Also the use of irq_of_parse_and_map is replaced by platform_get_irq. Signed-off-by: Martin Sperl Signed-off-by: Mark Brown --- drivers/spi/spi-bcm2835aux.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/spi') diff --git a/drivers/spi/spi-bcm2835aux.c b/drivers/spi/spi-bcm2835aux.c index 05d2d6e..7de6f84 100644 --- a/drivers/spi/spi-bcm2835aux.c +++ b/drivers/spi/spi-bcm2835aux.c @@ -429,7 +429,7 @@ static int bcm2835aux_spi_probe(struct platform_device *pdev) goto out_master_put; } - bs->irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + bs->irq = platform_get_irq(pdev, 0); if (bs->irq <= 0) { dev_err(&pdev->dev, "could not get IRQ: %d\n", bs->irq); err = bs->irq ? bs->irq : -ENODEV; @@ -451,6 +451,9 @@ static int bcm2835aux_spi_probe(struct platform_device *pdev) goto out_clk_disable; } + /* reset SPI-HW block */ + bcm2835aux_spi_reset_hw(bs); + err = devm_request_irq(&pdev->dev, bs->irq, bcm2835aux_spi_interrupt, IRQF_SHARED, @@ -460,9 +463,6 @@ static int bcm2835aux_spi_probe(struct platform_device *pdev) 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); -- cgit v1.1