aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2015-07-23 13:00:27 +1000
committerStephen Rothwell <sfr@canb.auug.org.au>2015-07-23 13:00:27 +1000
commita08dfafc597660dec6a88f367dba163ab9d46f03 (patch)
treec53acf8004557cefd057d72937121b8949393797
parentaed32ddf2335942e4730b8f5865601f12743828f (diff)
parent4b762b0e383c1ce22590093053f079d8a74df93e (diff)
Merge remote-tracking branch 'spi/for-next'
-rw-r--r--Documentation/devicetree/bindings/powerpc/fsl/mpc5121-psc.txt24
-rw-r--r--Documentation/devicetree/bindings/spi/spi-img-spfi.txt1
-rw-r--r--arch/powerpc/include/asm/mpc52xx_psc.h5
-rw-r--r--drivers/spi/Kconfig2
-rw-r--r--drivers/spi/spi-img-spfi.c16
-rw-r--r--drivers/spi/spi-mpc512x-psc.c70
-rw-r--r--drivers/spi/spi-pxa2xx-pci.c1
-rw-r--r--drivers/spi/spi-rockchip.c1
-rw-r--r--drivers/spi/spi-rspi.c19
-rw-r--r--drivers/spi/spi-s3c24xx.c1
-rw-r--r--drivers/spi/spi-sh-msiof.c5
-rw-r--r--drivers/spi/spi-xcomm.c1
-rw-r--r--drivers/spi/spi-zynqmp-gqspi.c1
-rw-r--r--drivers/spi/spi.c184
-rw-r--r--drivers/spi/spidev.c3
-rw-r--r--include/linux/spi/spi.h64
16 files changed, 342 insertions, 56 deletions
diff --git a/Documentation/devicetree/bindings/powerpc/fsl/mpc5121-psc.txt b/Documentation/devicetree/bindings/powerpc/fsl/mpc5121-psc.txt
index 8832e8798912..647817527c88 100644
--- a/Documentation/devicetree/bindings/powerpc/fsl/mpc5121-psc.txt
+++ b/Documentation/devicetree/bindings/powerpc/fsl/mpc5121-psc.txt
@@ -6,14 +6,14 @@ PSC in UART mode
For PSC in UART mode the needed PSC serial devices
are specified by fsl,mpc5121-psc-uart nodes in the
fsl,mpc5121-immr SoC node. Additionally the PSC FIFO
-Controller node fsl,mpc5121-psc-fifo is requered there:
+Controller node fsl,mpc5121-psc-fifo is required there:
-fsl,mpc5121-psc-uart nodes
+fsl,mpc512x-psc-uart nodes
--------------------------
Required properties :
- - compatible : Should contain "fsl,mpc5121-psc-uart" and "fsl,mpc5121-psc"
- - cell-index : Index of the PSC in hardware
+ - compatible : Should contain "fsl,<soc>-psc-uart" and "fsl,<soc>-psc"
+ Supported <soc>s: mpc5121, mpc5125
- reg : Offset and length of the register set for the PSC device
- interrupts : <a b> where a is the interrupt number of the
PSC FIFO Controller and b is a field that represents an
@@ -25,12 +25,21 @@ Recommended properties :
- fsl,rx-fifo-size : the size of the RX fifo slice (a multiple of 4)
- fsl,tx-fifo-size : the size of the TX fifo slice (a multiple of 4)
+PSC in SPI mode
+---------------
-fsl,mpc5121-psc-fifo node
+Similar to the UART mode a PSC can be operated in SPI mode. The compatible used
+for that is fsl,mpc5121-psc-spi. It requires a fsl,mpc5121-psc-fifo as well.
+The required and recommended properties are identical to the
+fsl,mpc5121-psc-uart nodes, just use spi instead of uart in the compatible
+string.
+
+fsl,mpc512x-psc-fifo node
-------------------------
Required properties :
- - compatible : Should be "fsl,mpc5121-psc-fifo"
+ - compatible : Should be "fsl,<soc>-psc-fifo"
+ Supported <soc>s: mpc5121, mpc5125
- reg : Offset and length of the register set for the PSC
FIFO Controller
- interrupts : <a b> where a is the interrupt number of the
@@ -39,6 +48,9 @@ Required properties :
- interrupt-parent : the phandle for the interrupt controller that
services interrupts for this device.
+Recommended properties :
+ - clocks : specifies the clock needed to operate the fifo controller
+ - clock-names : name(s) for the clock(s) listed in clocks
Example for a board using PSC0 and PSC1 devices in serial mode:
diff --git a/Documentation/devicetree/bindings/spi/spi-img-spfi.txt b/Documentation/devicetree/bindings/spi/spi-img-spfi.txt
index e02fbf18c82c..494db6012d02 100644
--- a/Documentation/devicetree/bindings/spi/spi-img-spfi.txt
+++ b/Documentation/devicetree/bindings/spi/spi-img-spfi.txt
@@ -21,6 +21,7 @@ Required properties:
Optional properties:
- img,supports-quad-mode: Should be set if the interface supports quad mode
SPI transfers.
+- spfi-max-frequency: Maximum speed supported by the spfi block.
Example:
diff --git a/arch/powerpc/include/asm/mpc52xx_psc.h b/arch/powerpc/include/asm/mpc52xx_psc.h
index d0ece257d310..04c7e8fc24c2 100644
--- a/arch/powerpc/include/asm/mpc52xx_psc.h
+++ b/arch/powerpc/include/asm/mpc52xx_psc.h
@@ -150,7 +150,10 @@
/* Structure of the hardware registers */
struct mpc52xx_psc {
- u8 mode; /* PSC + 0x00 */
+ union {
+ u8 mode; /* PSC + 0x00 */
+ u8 mr2;
+ };
u8 reserved0[3];
union { /* PSC + 0x04 */
u16 status;
diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 0cae1694014d..b0f30fb68914 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -612,7 +612,7 @@ config SPI_XTENSA_XTFPGA
config SPI_ZYNQMP_GQSPI
tristate "Xilinx ZynqMP GQSPI controller"
- depends on SPI_MASTER
+ depends on SPI_MASTER && HAS_DMA
help
Enables Xilinx GQSPI controller driver for Zynq UltraScale+ MPSoC.
diff --git a/drivers/spi/spi-img-spfi.c b/drivers/spi/spi-img-spfi.c
index 788e2b176a4f..00f1ac3d6a18 100644
--- a/drivers/spi/spi-img-spfi.c
+++ b/drivers/spi/spi-img-spfi.c
@@ -40,6 +40,7 @@
#define SPFI_CONTROL_SOFT_RESET BIT(11)
#define SPFI_CONTROL_SEND_DMA BIT(10)
#define SPFI_CONTROL_GET_DMA BIT(9)
+#define SPFI_CONTROL_SE BIT(8)
#define SPFI_CONTROL_TMODE_SHIFT 5
#define SPFI_CONTROL_TMODE_MASK 0x7
#define SPFI_CONTROL_TMODE_SINGLE 0
@@ -491,6 +492,7 @@ static void img_spfi_config(struct spi_master *master, struct spi_device *spi,
else if (xfer->tx_nbits == SPI_NBITS_QUAD &&
xfer->rx_nbits == SPI_NBITS_QUAD)
val |= SPFI_CONTROL_TMODE_QUAD << SPFI_CONTROL_TMODE_SHIFT;
+ val |= SPFI_CONTROL_SE;
spfi_writel(spfi, val, SPFI_CONTROL);
}
@@ -546,6 +548,7 @@ static int img_spfi_probe(struct platform_device *pdev)
struct img_spfi *spfi;
struct resource *res;
int ret;
+ u32 max_speed_hz;
master = spi_alloc_master(&pdev->dev, sizeof(*spfi));
if (!master)
@@ -610,6 +613,19 @@ static int img_spfi_probe(struct platform_device *pdev)
master->max_speed_hz = clk_get_rate(spfi->spfi_clk) / 4;
master->min_speed_hz = clk_get_rate(spfi->spfi_clk) / 512;
+ /*
+ * Maximum speed supported by spfi is limited to the lower value
+ * between 1/4 of the SPFI clock or to "spfi-max-frequency"
+ * defined in the device tree.
+ * If no value is defined in the device tree assume the maximum
+ * speed supported to be 1/4 of the SPFI clock.
+ */
+ if (!of_property_read_u32(spfi->dev->of_node, "spfi-max-frequency",
+ &max_speed_hz)) {
+ if (master->max_speed_hz > max_speed_hz)
+ master->max_speed_hz = max_speed_hz;
+ }
+
master->setup = img_spfi_setup;
master->cleanup = img_spfi_cleanup;
master->transfer_one = img_spfi_transfer_one;
diff --git a/drivers/spi/spi-mpc512x-psc.c b/drivers/spi/spi-mpc512x-psc.c
index 965d2bdcfdcc..1e75341689a6 100644
--- a/drivers/spi/spi-mpc512x-psc.c
+++ b/drivers/spi/spi-mpc512x-psc.c
@@ -30,11 +30,37 @@
#include <linux/gpio.h>
#include <asm/mpc52xx_psc.h>
+enum {
+ TYPE_MPC5121,
+ TYPE_MPC5125,
+};
+
+/*
+ * This macro abstracts the differences in the PSC register layout between
+ * MPC5121 (which uses a struct mpc52xx_psc) and MPC5125 (using mpc5125_psc).
+ */
+#define psc_addr(mps, regname) ({ \
+ void *__ret = NULL; \
+ switch (mps->type) { \
+ case TYPE_MPC5121: { \
+ struct mpc52xx_psc __iomem *psc = mps->psc; \
+ __ret = &psc->regname; \
+ }; \
+ break; \
+ case TYPE_MPC5125: { \
+ struct mpc5125_psc __iomem *psc = mps->psc; \
+ __ret = &psc->regname; \
+ }; \
+ break; \
+ } \
+ __ret; })
+
struct mpc512x_psc_spi {
void (*cs_control)(struct spi_device *spi, bool on);
/* driver internal data */
- struct mpc52xx_psc __iomem *psc;
+ int type;
+ void __iomem *psc;
struct mpc512x_psc_fifo __iomem *fifo;
unsigned int irq;
u8 bits_per_word;
@@ -71,13 +97,12 @@ static void mpc512x_psc_spi_activate_cs(struct spi_device *spi)
{
struct mpc512x_psc_spi_cs *cs = spi->controller_state;
struct mpc512x_psc_spi *mps = spi_master_get_devdata(spi->master);
- struct mpc52xx_psc __iomem *psc = mps->psc;
u32 sicr;
u32 ccr;
int speed;
u16 bclkdiv;
- sicr = in_be32(&psc->sicr);
+ sicr = in_be32(psc_addr(mps, sicr));
/* Set clock phase and polarity */
if (spi->mode & SPI_CPHA)
@@ -94,9 +119,9 @@ static void mpc512x_psc_spi_activate_cs(struct spi_device *spi)
sicr |= 0x10000000;
else
sicr &= ~0x10000000;
- out_be32(&psc->sicr, sicr);
+ out_be32(psc_addr(mps, sicr), sicr);
- ccr = in_be32(&psc->ccr);
+ ccr = in_be32(psc_addr(mps, ccr));
ccr &= 0xFF000000;
speed = cs->speed_hz;
if (!speed)
@@ -104,7 +129,7 @@ static void mpc512x_psc_spi_activate_cs(struct spi_device *spi)
bclkdiv = (mps->mclk_rate / speed) - 1;
ccr |= (((bclkdiv & 0xff) << 16) | (((bclkdiv >> 8) & 0xff) << 8));
- out_be32(&psc->ccr, ccr);
+ out_be32(psc_addr(mps, ccr), ccr);
mps->bits_per_word = cs->bits_per_word;
if (mps->cs_control && gpio_is_valid(spi->cs_gpio))
@@ -315,16 +340,15 @@ static int mpc512x_psc_spi_msg_xfer(struct spi_master *master,
static int mpc512x_psc_spi_prep_xfer_hw(struct spi_master *master)
{
struct mpc512x_psc_spi *mps = spi_master_get_devdata(master);
- struct mpc52xx_psc __iomem *psc = mps->psc;
dev_dbg(&master->dev, "%s()\n", __func__);
/* Zero MR2 */
- in_8(&psc->mode);
- out_8(&psc->mode, 0x0);
+ in_8(psc_addr(mps, mr2));
+ out_8(psc_addr(mps, mr2), 0x0);
/* enable transmitter/receiver */
- out_8(&psc->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
+ out_8(psc_addr(mps, command), MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
return 0;
}
@@ -332,13 +356,12 @@ static int mpc512x_psc_spi_prep_xfer_hw(struct spi_master *master)
static int mpc512x_psc_spi_unprep_xfer_hw(struct spi_master *master)
{
struct mpc512x_psc_spi *mps = spi_master_get_devdata(master);
- struct mpc52xx_psc __iomem *psc = mps->psc;
struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
dev_dbg(&master->dev, "%s()\n", __func__);
/* disable transmitter/receiver and fifo interrupt */
- out_8(&psc->command, MPC52xx_PSC_TX_DISABLE | MPC52xx_PSC_RX_DISABLE);
+ out_8(psc_addr(mps, command), MPC52xx_PSC_TX_DISABLE | MPC52xx_PSC_RX_DISABLE);
out_be32(&fifo->tximr, 0);
return 0;
@@ -388,7 +411,6 @@ static void mpc512x_psc_spi_cleanup(struct spi_device *spi)
static int mpc512x_psc_spi_port_config(struct spi_master *master,
struct mpc512x_psc_spi *mps)
{
- struct mpc52xx_psc __iomem *psc = mps->psc;
struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
u32 sicr;
u32 ccr;
@@ -396,12 +418,12 @@ static int mpc512x_psc_spi_port_config(struct spi_master *master,
u16 bclkdiv;
/* Reset the PSC into a known state */
- out_8(&psc->command, MPC52xx_PSC_RST_RX);
- out_8(&psc->command, MPC52xx_PSC_RST_TX);
- out_8(&psc->command, MPC52xx_PSC_TX_DISABLE | MPC52xx_PSC_RX_DISABLE);
+ out_8(psc_addr(mps, command), MPC52xx_PSC_RST_RX);
+ out_8(psc_addr(mps, command), MPC52xx_PSC_RST_TX);
+ out_8(psc_addr(mps, command), MPC52xx_PSC_TX_DISABLE | MPC52xx_PSC_RX_DISABLE);
/* Disable psc interrupts all useful interrupts are in fifo */
- out_be16(&psc->isr_imr.imr, 0);
+ out_be16(psc_addr(mps, isr_imr.imr), 0);
/* Disable fifo interrupts, will be enabled later */
out_be32(&fifo->tximr, 0);
@@ -417,18 +439,18 @@ static int mpc512x_psc_spi_port_config(struct spi_master *master,
0x00004000 | /* MSTR = 1 -- SPI master */
0x00000800; /* UseEOF = 1 -- SS low until EOF */
- out_be32(&psc->sicr, sicr);
+ out_be32(psc_addr(mps, sicr), sicr);
- ccr = in_be32(&psc->ccr);
+ ccr = in_be32(psc_addr(mps, ccr));
ccr &= 0xFF000000;
speed = 1000000; /* default 1MHz */
bclkdiv = (mps->mclk_rate / speed) - 1;
ccr |= (((bclkdiv & 0xff) << 16) | (((bclkdiv >> 8) & 0xff) << 8));
- out_be32(&psc->ccr, ccr);
+ out_be32(psc_addr(mps, ccr), ccr);
/* Set 2ms DTL delay */
- out_8(&psc->ctur, 0x00);
- out_8(&psc->ctlr, 0x82);
+ out_8(psc_addr(mps, ctur), 0x00);
+ out_8(psc_addr(mps, ctlr), 0x82);
/* we don't use the alarms */
out_be32(&fifo->rxalarm, 0xfff);
@@ -482,6 +504,7 @@ static int mpc512x_psc_spi_do_probe(struct device *dev, u32 regaddr,
dev_set_drvdata(dev, master);
mps = spi_master_get_devdata(master);
+ mps->type = (int)of_device_get_match_data(dev);
mps->irq = irq;
if (pdata == NULL) {
@@ -589,7 +612,8 @@ static int mpc512x_psc_spi_of_remove(struct platform_device *op)
}
static const struct of_device_id mpc512x_psc_spi_of_match[] = {
- { .compatible = "fsl,mpc5121-psc-spi", },
+ { .compatible = "fsl,mpc5121-psc-spi", .data = (void *)TYPE_MPC5121 },
+ { .compatible = "fsl,mpc5125-psc-spi", .data = (void *)TYPE_MPC5125 },
{},
};
diff --git a/drivers/spi/spi-pxa2xx-pci.c b/drivers/spi/spi-pxa2xx-pci.c
index 3cfd4357489a..d19d7f28aecb 100644
--- a/drivers/spi/spi-pxa2xx-pci.c
+++ b/drivers/spi/spi-pxa2xx-pci.c
@@ -7,7 +7,6 @@
#include <linux/of_device.h>
#include <linux/module.h>
#include <linux/spi/pxa2xx_spi.h>
-#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/dmaengine.h>
diff --git a/drivers/spi/spi-rockchip.c b/drivers/spi/spi-rockchip.c
index 68e7efeb9a27..79a8bc4f6cec 100644
--- a/drivers/spi/spi-rockchip.c
+++ b/drivers/spi/spi-rockchip.c
@@ -645,7 +645,6 @@ static int rockchip_spi_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, master);
rs = spi_master_get_devdata(master);
- memset(rs, 0, sizeof(struct rockchip_spi));
/* Get basic io resource and map it */
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
diff --git a/drivers/spi/spi-rspi.c b/drivers/spi/spi-rspi.c
index f9189a0c8cec..818843336932 100644
--- a/drivers/spi/spi-rspi.c
+++ b/drivers/spi/spi-rspi.c
@@ -383,7 +383,8 @@ static void qspi_update(const struct rspi_data *rspi, u8 mask, u8 val, u8 reg)
rspi_write8(rspi, data, reg);
}
-static int qspi_set_send_trigger(struct rspi_data *rspi, unsigned int len)
+static unsigned int qspi_set_send_trigger(struct rspi_data *rspi,
+ unsigned int len)
{
unsigned int n;
@@ -724,25 +725,25 @@ static int rspi_rz_transfer_one(struct spi_master *master,
static int qspi_trigger_transfer_out_in(struct rspi_data *rspi, const u8 *tx,
u8 *rx, unsigned int len)
{
- int i, n, ret;
- int error;
+ unsigned int i, n;
+ int ret;
while (len > 0) {
n = qspi_set_send_trigger(rspi, len);
qspi_set_receive_trigger(rspi, len);
if (n == QSPI_BUFFER_SIZE) {
- error = rspi_wait_for_tx_empty(rspi);
- if (error < 0) {
+ ret = rspi_wait_for_tx_empty(rspi);
+ if (ret < 0) {
dev_err(&rspi->master->dev, "transmit timeout\n");
- return error;
+ return ret;
}
for (i = 0; i < n; i++)
rspi_write_data(rspi, *tx++);
- error = rspi_wait_for_rx_full(rspi);
- if (error < 0) {
+ ret = rspi_wait_for_rx_full(rspi);
+ if (ret < 0) {
dev_err(&rspi->master->dev, "receive timeout\n");
- return error;
+ return ret;
}
for (i = 0; i < n; i++)
*rx++ = rspi_read_data(rspi);
diff --git a/drivers/spi/spi-s3c24xx.c b/drivers/spi/spi-s3c24xx.c
index f747ca269986..f36bc320a807 100644
--- a/drivers/spi/spi-s3c24xx.c
+++ b/drivers/spi/spi-s3c24xx.c
@@ -501,7 +501,6 @@ static int s3c24xx_spi_probe(struct platform_device *pdev)
}
hw = spi_master_get_devdata(master);
- memset(hw, 0, sizeof(struct s3c24xx_spi));
hw->master = master;
hw->pdata = pdata = dev_get_platdata(&pdev->dev);
diff --git a/drivers/spi/spi-sh-msiof.c b/drivers/spi/spi-sh-msiof.c
index d3370a612d84..118c652d88b5 100644
--- a/drivers/spi/spi-sh-msiof.c
+++ b/drivers/spi/spi-sh-msiof.c
@@ -1265,11 +1265,6 @@ static int sh_msiof_spi_remove(struct platform_device *pdev)
static const struct platform_device_id spi_driver_ids[] = {
{ "spi_sh_msiof", (kernel_ulong_t)&sh_data },
- { "spi_r8a7790_msiof", (kernel_ulong_t)&r8a779x_data },
- { "spi_r8a7791_msiof", (kernel_ulong_t)&r8a779x_data },
- { "spi_r8a7792_msiof", (kernel_ulong_t)&r8a779x_data },
- { "spi_r8a7793_msiof", (kernel_ulong_t)&r8a779x_data },
- { "spi_r8a7794_msiof", (kernel_ulong_t)&r8a779x_data },
{},
};
MODULE_DEVICE_TABLE(platform, spi_driver_ids);
diff --git a/drivers/spi/spi-xcomm.c b/drivers/spi/spi-xcomm.c
index bb478dccf1d8..d0561a8d9f16 100644
--- a/drivers/spi/spi-xcomm.c
+++ b/drivers/spi/spi-xcomm.c
@@ -241,7 +241,6 @@ static const struct i2c_device_id spi_xcomm_ids[] = {
static struct i2c_driver spi_xcomm_driver = {
.driver = {
.name = "spi-xcomm",
- .owner = THIS_MODULE,
},
.id_table = spi_xcomm_ids,
.probe = spi_xcomm_probe,
diff --git a/drivers/spi/spi-zynqmp-gqspi.c b/drivers/spi/spi-zynqmp-gqspi.c
index 87b20a511a6b..f23f36ebaf3d 100644
--- a/drivers/spi/spi-zynqmp-gqspi.c
+++ b/drivers/spi/spi-zynqmp-gqspi.c
@@ -214,6 +214,7 @@ static void zynqmp_gqspi_selectslave(struct zynqmp_qspi *instanceptr,
case GQSPI_SELECT_FLASH_CS_BOTH:
instanceptr->genfifocs = GQSPI_GENFIFO_CS_LOWER |
GQSPI_GENFIFO_CS_UPPER;
+ break;
case GQSPI_SELECT_FLASH_CS_UPPER:
instanceptr->genfifocs = GQSPI_GENFIFO_CS_UPPER;
break;
diff --git a/drivers/spi/spi.c b/drivers/spi/spi.c
index cf8b91b23a76..766f48b9b70d 100644
--- a/drivers/spi/spi.c
+++ b/drivers/spi/spi.c
@@ -67,11 +67,141 @@ modalias_show(struct device *dev, struct device_attribute *a, char *buf)
}
static DEVICE_ATTR_RO(modalias);
+#define SPI_STATISTICS_ATTRS(field, file) \
+static ssize_t spi_master_##field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct spi_master *master = container_of(dev, \
+ struct spi_master, dev); \
+ return spi_statistics_##field##_show(&master->statistics, buf); \
+} \
+static struct device_attribute dev_attr_spi_master_##field = { \
+ .attr = { .name = file, .mode = S_IRUGO }, \
+ .show = spi_master_##field##_show, \
+}; \
+static ssize_t spi_device_##field##_show(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ struct spi_device *spi = container_of(dev, \
+ struct spi_device, dev); \
+ return spi_statistics_##field##_show(&spi->statistics, buf); \
+} \
+static struct device_attribute dev_attr_spi_device_##field = { \
+ .attr = { .name = file, .mode = S_IRUGO }, \
+ .show = spi_device_##field##_show, \
+}
+
+#define SPI_STATISTICS_SHOW_NAME(name, file, field, format_string) \
+static ssize_t spi_statistics_##name##_show(struct spi_statistics *stat, \
+ char *buf) \
+{ \
+ unsigned long flags; \
+ ssize_t len; \
+ spin_lock_irqsave(&stat->lock, flags); \
+ len = sprintf(buf, format_string, stat->field); \
+ spin_unlock_irqrestore(&stat->lock, flags); \
+ return len; \
+} \
+SPI_STATISTICS_ATTRS(name, file)
+
+#define SPI_STATISTICS_SHOW(field, format_string) \
+ SPI_STATISTICS_SHOW_NAME(field, __stringify(field), \
+ field, format_string)
+
+SPI_STATISTICS_SHOW(messages, "%lu");
+SPI_STATISTICS_SHOW(transfers, "%lu");
+SPI_STATISTICS_SHOW(errors, "%lu");
+SPI_STATISTICS_SHOW(timedout, "%lu");
+
+SPI_STATISTICS_SHOW(spi_sync, "%lu");
+SPI_STATISTICS_SHOW(spi_sync_immediate, "%lu");
+SPI_STATISTICS_SHOW(spi_async, "%lu");
+
+SPI_STATISTICS_SHOW(bytes, "%llu");
+SPI_STATISTICS_SHOW(bytes_rx, "%llu");
+SPI_STATISTICS_SHOW(bytes_tx, "%llu");
+
static struct attribute *spi_dev_attrs[] = {
&dev_attr_modalias.attr,
NULL,
};
-ATTRIBUTE_GROUPS(spi_dev);
+
+static const struct attribute_group spi_dev_group = {
+ .attrs = spi_dev_attrs,
+};
+
+static struct attribute *spi_device_statistics_attrs[] = {
+ &dev_attr_spi_device_messages.attr,
+ &dev_attr_spi_device_transfers.attr,
+ &dev_attr_spi_device_errors.attr,
+ &dev_attr_spi_device_timedout.attr,
+ &dev_attr_spi_device_spi_sync.attr,
+ &dev_attr_spi_device_spi_sync_immediate.attr,
+ &dev_attr_spi_device_spi_async.attr,
+ &dev_attr_spi_device_bytes.attr,
+ &dev_attr_spi_device_bytes_rx.attr,
+ &dev_attr_spi_device_bytes_tx.attr,
+ NULL,
+};
+
+static const struct attribute_group spi_device_statistics_group = {
+ .name = "statistics",
+ .attrs = spi_device_statistics_attrs,
+};
+
+static const struct attribute_group *spi_dev_groups[] = {
+ &spi_dev_group,
+ &spi_device_statistics_group,
+ NULL,
+};
+
+static struct attribute *spi_master_statistics_attrs[] = {
+ &dev_attr_spi_master_messages.attr,
+ &dev_attr_spi_master_transfers.attr,
+ &dev_attr_spi_master_errors.attr,
+ &dev_attr_spi_master_timedout.attr,
+ &dev_attr_spi_master_spi_sync.attr,
+ &dev_attr_spi_master_spi_sync_immediate.attr,
+ &dev_attr_spi_master_spi_async.attr,
+ &dev_attr_spi_master_bytes.attr,
+ &dev_attr_spi_master_bytes_rx.attr,
+ &dev_attr_spi_master_bytes_tx.attr,
+ NULL,
+};
+
+static const struct attribute_group spi_master_statistics_group = {
+ .name = "statistics",
+ .attrs = spi_master_statistics_attrs,
+};
+
+static const struct attribute_group *spi_master_groups[] = {
+ &spi_master_statistics_group,
+ NULL,
+};
+
+void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
+ struct spi_transfer *xfer,
+ struct spi_master *master)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&stats->lock, flags);
+
+ stats->transfers++;
+
+ stats->bytes += xfer->len;
+ if ((xfer->tx_buf) &&
+ (xfer->tx_buf != master->dummy_tx))
+ stats->bytes_tx += xfer->len;
+ if ((xfer->rx_buf) &&
+ (xfer->rx_buf != master->dummy_rx))
+ stats->bytes_rx += xfer->len;
+
+ spin_unlock_irqrestore(&stats->lock, flags);
+}
+EXPORT_SYMBOL_GPL(spi_statistics_add_transfer_stats);
/* modalias support makes "modprobe $MODALIAS" new-style hotplug work,
* and the sysfs version makes coldplug work too.
@@ -249,6 +379,9 @@ struct spi_device *spi_alloc_device(struct spi_master *master)
spi->dev.bus = &spi_bus_type;
spi->dev.release = spidev_release;
spi->cs_gpio = -ENOENT;
+
+ spin_lock_init(&spi->statistics.lock);
+
device_initialize(&spi->dev);
return spi;
}
@@ -476,21 +609,30 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
enum dma_data_direction dir)
{
const bool vmalloced_buf = is_vmalloc_addr(buf);
- const int desc_len = vmalloced_buf ? PAGE_SIZE : master->max_dma_len;
- const int sgs = DIV_ROUND_UP(len, desc_len);
+ int desc_len;
+ int sgs;
struct page *vm_page;
void *sg_buf;
size_t min;
int i, ret;
+ if (vmalloced_buf) {
+ desc_len = PAGE_SIZE;
+ sgs = DIV_ROUND_UP(len + offset_in_page(buf), desc_len);
+ } else {
+ desc_len = master->max_dma_len;
+ sgs = DIV_ROUND_UP(len, desc_len);
+ }
+
ret = sg_alloc_table(sgt, sgs, GFP_KERNEL);
if (ret != 0)
return ret;
for (i = 0; i < sgs; i++) {
- min = min_t(size_t, len, desc_len);
if (vmalloced_buf) {
+ min = min_t(size_t,
+ len, desc_len - offset_in_page(buf));
vm_page = vmalloc_to_page(buf);
if (!vm_page) {
sg_free_table(sgt);
@@ -499,6 +641,7 @@ static int spi_map_buf(struct spi_master *master, struct device *dev,
sg_set_page(&sgt->sgl[i], vm_page,
min, offset_in_page(buf));
} else {
+ min = min_t(size_t, len, desc_len);
sg_buf = buf;
sg_set_buf(&sgt->sgl[i], sg_buf, min);
}
@@ -689,17 +832,29 @@ static int spi_transfer_one_message(struct spi_master *master,
bool keep_cs = false;
int ret = 0;
unsigned long ms = 1;
+ struct spi_statistics *statm = &master->statistics;
+ struct spi_statistics *stats = &msg->spi->statistics;
spi_set_cs(msg->spi, true);
+ SPI_STATISTICS_INCREMENT_FIELD(statm, messages);
+ SPI_STATISTICS_INCREMENT_FIELD(stats, messages);
+
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
trace_spi_transfer_start(msg, xfer);
+ spi_statistics_add_transfer_stats(statm, xfer, master);
+ spi_statistics_add_transfer_stats(stats, xfer, master);
+
if (xfer->tx_buf || xfer->rx_buf) {
reinit_completion(&master->xfer_completion);
ret = master->transfer_one(master, msg->spi, xfer);
if (ret < 0) {
+ SPI_STATISTICS_INCREMENT_FIELD(statm,
+ errors);
+ SPI_STATISTICS_INCREMENT_FIELD(stats,
+ errors);
dev_err(&msg->spi->dev,
"SPI transfer failed: %d\n", ret);
goto out;
@@ -715,6 +870,10 @@ static int spi_transfer_one_message(struct spi_master *master,
}
if (ms == 0) {
+ SPI_STATISTICS_INCREMENT_FIELD(statm,
+ timedout);
+ SPI_STATISTICS_INCREMENT_FIELD(stats,
+ timedout);
dev_err(&msg->spi->dev,
"SPI transfer timed out\n");
msg->status = -ETIMEDOUT;
@@ -1416,10 +1575,10 @@ static struct class spi_master_class = {
.name = "spi_master",
.owner = THIS_MODULE,
.dev_release = spi_master_release,
+ .dev_groups = spi_master_groups,
};
-
/**
* spi_alloc_master - allocate SPI master controller
* @dev: the controller, possibly using the platform_bus
@@ -1585,6 +1744,8 @@ int spi_register_master(struct spi_master *master)
goto done;
}
}
+ /* add statistics */
+ spin_lock_init(&master->statistics.lock);
mutex_lock(&board_lock);
list_add_tail(&master->list, &spi_master_list);
@@ -1939,6 +2100,9 @@ static int __spi_async(struct spi_device *spi, struct spi_message *message)
message->spi = spi;
+ SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_async);
+ SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_async);
+
trace_spi_message_submit(message);
return master->transfer(spi, message);
@@ -2075,6 +2239,9 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
message->context = &done;
message->spi = spi;
+ SPI_STATISTICS_INCREMENT_FIELD(&master->statistics, spi_sync);
+ SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics, spi_sync);
+
if (!bus_locked)
mutex_lock(&master->bus_lock_mutex);
@@ -2102,8 +2269,13 @@ static int __spi_sync(struct spi_device *spi, struct spi_message *message,
/* Push out the messages in the calling context if we
* can.
*/
- if (master->transfer == spi_queued_transfer)
+ if (master->transfer == spi_queued_transfer) {
+ SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
+ spi_sync_immediate);
+ SPI_STATISTICS_INCREMENT_FIELD(&spi->statistics,
+ spi_sync_immediate);
__spi_pump_messages(master, false);
+ }
wait_for_completion(&done);
status = message->status;
diff --git a/drivers/spi/spidev.c b/drivers/spi/spidev.c
index dd616ff0ffc5..a902cc282de9 100644
--- a/drivers/spi/spidev.c
+++ b/drivers/spi/spidev.c
@@ -693,6 +693,7 @@ static struct class *spidev_class;
#ifdef CONFIG_OF
static const struct of_device_id spidev_dt_ids[] = {
{ .compatible = "rohm,dh2228fv" },
+ { .compatible = "lineartechnology,ltc2488" },
{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
@@ -708,7 +709,7 @@ static int spidev_probe(struct spi_device *spi)
/*
* spidev should never be referenced in DT without a specific
- * compatbile string, it is a Linux implementation thing
+ * compatible string, it is a Linux implementation thing
* rather than a description of the hardware.
*/
if (spi->dev.of_node && !of_match_device(spidev_dt_ids, &spi->dev)) {
diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h
index d673072346f2..269e8afd3e2a 100644
--- a/include/linux/spi/spi.h
+++ b/include/linux/spi/spi.h
@@ -23,6 +23,8 @@
#include <linux/scatterlist.h>
struct dma_chan;
+struct spi_master;
+struct spi_transfer;
/*
* INTERFACES between SPI master-side drivers and SPI infrastructure.
@@ -31,6 +33,59 @@ struct dma_chan;
extern struct bus_type spi_bus_type;
/**
+ * struct spi_statistics - statistics for spi transfers
+ * @clock: lock protecting this structure
+ *
+ * @messages: number of spi-messages handled
+ * @transfers: number of spi_transfers handled
+ * @errors: number of errors during spi_transfer
+ * @timedout: number of timeouts during spi_transfer
+ *
+ * @spi_sync: number of times spi_sync is used
+ * @spi_sync_immediate:
+ * number of times spi_sync is executed immediately
+ * in calling context without queuing and scheduling
+ * @spi_async: number of times spi_async is used
+ *
+ * @bytes: number of bytes transferred to/from device
+ * @bytes_tx: number of bytes sent to device
+ * @bytes_rx: number of bytes received from device
+ *
+ */
+struct spi_statistics {
+ spinlock_t lock; /* lock for the whole structure */
+
+ unsigned long messages;
+ unsigned long transfers;
+ unsigned long errors;
+ unsigned long timedout;
+
+ unsigned long spi_sync;
+ unsigned long spi_sync_immediate;
+ unsigned long spi_async;
+
+ unsigned long long bytes;
+ unsigned long long bytes_rx;
+ unsigned long long bytes_tx;
+
+};
+
+void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
+ struct spi_transfer *xfer,
+ struct spi_master *master);
+
+#define SPI_STATISTICS_ADD_TO_FIELD(stats, field, count) \
+ do { \
+ unsigned long flags; \
+ spin_lock_irqsave(&(stats)->lock, flags); \
+ (stats)->field += count; \
+ spin_unlock_irqrestore(&(stats)->lock, flags); \
+ } while (0)
+
+#define SPI_STATISTICS_INCREMENT_FIELD(stats, field) \
+ SPI_STATISTICS_ADD_TO_FIELD(stats, field, 1)
+
+/**
* struct spi_device - Master side proxy for an SPI slave device
* @dev: Driver model representation of the device.
* @master: SPI controller used with the device.
@@ -60,6 +115,8 @@ extern struct bus_type spi_bus_type;
* @cs_gpio: gpio number of the chipselect line (optional, -ENOENT when
* when not using a GPIO line)
*
+ * @statistics: statistics for the spi_device
+ *
* A @spi_device is used to interchange data between an SPI slave
* (usually a discrete chip) and CPU memory.
*
@@ -98,6 +155,9 @@ struct spi_device {
char modalias[SPI_NAME_SIZE];
int cs_gpio; /* chip select gpio */
+ /* the statistics */
+ struct spi_statistics statistics;
+
/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
@@ -296,6 +356,7 @@ static inline void spi_unregister_driver(struct spi_driver *sdrv)
* @cs_gpios: Array of GPIOs to use as chip select lines; one per CS
* number. Any individual value may be -ENOENT for CS lines that
* are not GPIOs (driven by the SPI controller itself).
+ * @statistics: statistics for the spi_master
* @dma_tx: DMA transmit channel
* @dma_rx: DMA receive channel
* @dummy_rx: dummy receive buffer for full-duplex devices
@@ -452,6 +513,9 @@ struct spi_master {
/* gpio chip select */
int *cs_gpios;
+ /* statistics */
+ struct spi_statistics statistics;
+
/* DMA channels for use with core dmaengine helpers */
struct dma_chan *dma_tx;
struct dma_chan *dma_rx;