cregit-Linux how code gets into the kernel

Release 4.7 drivers/spi/spi-bcm2835aux.c

Directory: drivers/spi
/*
 * 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_IN_RISING	0x00000400

#define BCM2835_AUX_SPI_CNTL0_CLEARFIFO	0x00000200

#define BCM2835_AUX_SPI_CNTL0_OUT_RISING	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_TXEMPTY	0x00000080

#define BCM2835_AUX_SPI_CNTL1_IDLE	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


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;
	
int pending;
};


static inline u32 bcm2835aux_rd(struct bcm2835aux_spi *bs, unsigned reg) { return readl(bs->regs + reg); }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl25100.00%1100.00%
Total25100.00%1100.00%


static inline void bcm2835aux_wr(struct bcm2835aux_spi *bs, unsigned reg, u32 val) { writel(val, bs->regs + reg); }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl29100.00%1100.00%
Total29100.00%1100.00%


static inline void bcm2835aux_rd_fifo(struct bcm2835aux_spi *bs) { u32 data; int count = min(bs->rx_len, 3); data = bcm2835aux_rd(bs, BCM2835_AUX_SPI_IO); if (bs->rx_buf) { 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; }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl134100.00%2100.00%
Total134100.00%2100.00%


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; bs->pending += 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); }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl133100.00%2100.00%
Total133100.00%2100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl30100.00%1100.00%
Total30100.00%1100.00%


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 && (bs->pending < 12) && (!(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; } if (!bs->tx_len) { /* disable tx fifo empty interrupt */ bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1] | BCM2835_AUX_SPI_CNTL1_IDLE); } /* and if rx_len is 0 then disable interrupts and wake up completion */ if (!bs->rx_len) { bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1]); complete(&master->xfer_completion); } /* and return */ return ret; }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl16481.59%250.00%
stephan olbrichstephan olbrich3718.41%250.00%
Total201100.00%4100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl54100.00%1100.00%
Total54100.00%1100.00%


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) && (bs->pending < 12) && (!(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); }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl109100.00%2100.00%
Total109100.00%2100.00%


static int bcm2835aux_spi_transfer_one_poll(struct spi_master *master, struct spi_device *spi, struct spi_transfer *tfr) { 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); } } /* and return without waiting for completion */ return 0; }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl207100.00%1100.00%
Total207100.00%1100.00%


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; unsigned long long 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 */ /* 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; } /* mask out old speed from previous spi_transfer */ bs->cntl[0] &= ~(BCM2835_AUX_SPI_CNTL0_SPEED); /* set the new speed */ bs->cntl[0] |= speed << BCM2835_AUX_SPI_CNTL0_SPEED_SHIFT; spi_used_hz = clk_hz / (2 * (speed + 1)); /* 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; bs->pending = 0; /* 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); /* run in interrupt mode for all others */ return bcm2835aux_spi_transfer_one_irq(master, spi, tfr); }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl22794.19%266.67%
stephan olbrichstephan olbrich145.81%133.33%
Total241100.00%3100.00%


static int bcm2835aux_spi_prepare_message(struct spi_master *master, struct spi_message *msg) { struct spi_device *spi = msg->spi; struct bcm2835aux_spi *bs = spi_master_get_devdata(master); 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; /* handle all the modes */ if (spi->mode & SPI_CPOL) { bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_CPOL; bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_OUT_RISING; } else { bs->cntl[0] |= BCM2835_AUX_SPI_CNTL0_IN_RISING; } bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL1, bs->cntl[1]); bcm2835aux_wr(bs, BCM2835_AUX_SPI_CNTL0, bs->cntl[0]); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
stephan olbrichstephan olbrich129100.00%2100.00%
Total129100.00%2100.00%


static int bcm2835aux_spi_unprepare_message(struct spi_master *master, struct spi_message *msg) { struct bcm2835aux_spi *bs = spi_master_get_devdata(master); bcm2835aux_spi_reset_hw(bs); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
stephan olbrichstephan olbrich34100.00%1100.00%
Total34100.00%1100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl31100.00%1100.00%
Total31100.00%1100.00%


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 = (SPI_CPOL | SPI_CS_HIGH | SPI_NO_CS); 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->prepare_message = bcm2835aux_spi_prepare_message; master->unprepare_message = bcm2835aux_spi_unprepare_message; 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 = 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; 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; } /* reset SPI-HW block */ bcm2835aux_spi_reset_hw(bs); 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; } 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; }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl46396.06%250.00%
stephan olbrichstephan olbrich193.94%250.00%
Total482100.00%4100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl47100.00%1100.00%
Total47100.00%1100.00%

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");

Overall Contributors

PersonTokensPropCommitsCommitProp
martin sperlmartin sperl196789.25%342.86%
stephan olbrichstephan olbrich23710.75%457.14%
Total2204100.00%7100.00%
Directory: drivers/spi
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
{% endraw %}