Merge remote-tracking branches 'spi/topic/lp8841', 'spi/topic/msg', 'spi/topic/pl022' and 'spi/topic/pxa2xx' into spi-next
This commit is contained in:
commit
b9facea19b
|
@ -0,0 +1,54 @@
|
|||
* ICP DAS LP-8841 SPI Controller for RTC
|
||||
|
||||
ICP DAS LP-8841 contains a DS-1302 RTC. RTC is connected to an IO
|
||||
memory register, which acts as an SPI master device.
|
||||
|
||||
The device uses the standard MicroWire half-duplex transfer timing.
|
||||
Master output is set on low clock and sensed by the RTC on the rising
|
||||
edge. Master input is set by the RTC on the trailing edge and is sensed
|
||||
by the master on low clock.
|
||||
|
||||
Required properties:
|
||||
|
||||
- #address-cells: should be 1
|
||||
|
||||
- #size-cells: should be 0
|
||||
|
||||
- compatible: should be "icpdas,lp8841-spi-rtc"
|
||||
|
||||
- reg: should provide IO memory address
|
||||
|
||||
Requirements to SPI slave nodes:
|
||||
|
||||
- There can be only one slave device.
|
||||
|
||||
- The spi slave node should claim the following flags which are
|
||||
required by the spi controller.
|
||||
|
||||
- spi-3wire: The master itself has only 3 wire. It cannor work in
|
||||
full duplex mode.
|
||||
|
||||
- spi-cs-high: DS-1302 has active high chip select line. The master
|
||||
doesn't support active low.
|
||||
|
||||
- spi-lsb-first: DS-1302 requires least significant bit first
|
||||
transfers. The master only support this type of bit ordering.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
spi@901c {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "icpdas,lp8841-spi-rtc";
|
||||
reg = <0x901c 0x1>;
|
||||
|
||||
rtc@0 {
|
||||
compatible = "maxim,ds1302";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <500000>;
|
||||
spi-3wire;
|
||||
spi-lsb-first;
|
||||
spi-cs-high;
|
||||
};
|
||||
};
|
|
@ -294,6 +294,16 @@ config SPI_LM70_LLP
|
|||
which interfaces to an LM70 temperature sensor using
|
||||
a parallel port.
|
||||
|
||||
config SPI_LP8841_RTC
|
||||
tristate "ICP DAS LP-8841 SPI Controller for RTC"
|
||||
depends on MACH_PXA27X_DT || COMPILE_TEST
|
||||
help
|
||||
This driver provides an SPI master device to drive Maxim
|
||||
DS-1302 real time clock.
|
||||
|
||||
Say N here unless you plan to run the kernel on an ICP DAS
|
||||
LP-8x4x industrial computer.
|
||||
|
||||
config SPI_MPC52xx
|
||||
tristate "Freescale MPC52xx SPI (non-PSC) controller support"
|
||||
depends on PPC_MPC52xx
|
||||
|
@ -445,10 +455,6 @@ config SPI_PPC4xx
|
|||
help
|
||||
This selects a driver for the PPC4xx SPI Controller.
|
||||
|
||||
config SPI_PXA2XX_DMA
|
||||
def_bool y
|
||||
depends on SPI_PXA2XX
|
||||
|
||||
config SPI_PXA2XX
|
||||
tristate "PXA2xx SSP SPI master"
|
||||
depends on (ARCH_PXA || PCI || ACPI)
|
||||
|
|
|
@ -47,6 +47,7 @@ obj-$(CONFIG_SPI_GPIO) += spi-gpio.o
|
|||
obj-$(CONFIG_SPI_IMG_SPFI) += spi-img-spfi.o
|
||||
obj-$(CONFIG_SPI_IMX) += spi-imx.o
|
||||
obj-$(CONFIG_SPI_LM70_LLP) += spi-lm70llp.o
|
||||
obj-$(CONFIG_SPI_LP8841_RTC) += spi-lp8841-rtc.o
|
||||
obj-$(CONFIG_SPI_MESON_SPIFC) += spi-meson-spifc.o
|
||||
obj-$(CONFIG_SPI_MPC512x_PSC) += spi-mpc512x-psc.o
|
||||
obj-$(CONFIG_SPI_MPC52xx_PSC) += spi-mpc52xx-psc.o
|
||||
|
@ -63,8 +64,7 @@ obj-$(CONFIG_SPI_TI_QSPI) += spi-ti-qspi.o
|
|||
obj-$(CONFIG_SPI_ORION) += spi-orion.o
|
||||
obj-$(CONFIG_SPI_PL022) += spi-pl022.o
|
||||
obj-$(CONFIG_SPI_PPC4xx) += spi-ppc4xx.o
|
||||
spi-pxa2xx-platform-objs := spi-pxa2xx.o
|
||||
spi-pxa2xx-platform-$(CONFIG_SPI_PXA2XX_DMA) += spi-pxa2xx-dma.o
|
||||
spi-pxa2xx-platform-objs := spi-pxa2xx.o spi-pxa2xx-dma.o
|
||||
obj-$(CONFIG_SPI_PXA2XX) += spi-pxa2xx-platform.o
|
||||
obj-$(CONFIG_SPI_PXA2XX_PCI) += spi-pxa2xx-pci.o
|
||||
obj-$(CONFIG_SPI_QUP) += spi-qup.o
|
||||
|
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* SPI master driver for ICP DAS LP-8841 RTC
|
||||
*
|
||||
* Copyright (C) 2016 Sergei Ianovich
|
||||
*
|
||||
* based on
|
||||
*
|
||||
* Dallas DS1302 RTC Support
|
||||
* Copyright (C) 2002 David McCullough
|
||||
* Copyright (C) 2003 - 2007 Paul Mundt
|
||||
*
|
||||
* 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/delay.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#define DRIVER_NAME "spi_lp8841_rtc"
|
||||
|
||||
#define SPI_LP8841_RTC_CE 0x01
|
||||
#define SPI_LP8841_RTC_CLK 0x02
|
||||
#define SPI_LP8841_RTC_nWE 0x04
|
||||
#define SPI_LP8841_RTC_MOSI 0x08
|
||||
#define SPI_LP8841_RTC_MISO 0x01
|
||||
|
||||
/*
|
||||
* REVISIT If there is support for SPI_3WIRE and SPI_LSB_FIRST in SPI
|
||||
* GPIO driver, this SPI driver can be replaced by a simple GPIO driver
|
||||
* providing 3 GPIO pins.
|
||||
*/
|
||||
|
||||
struct spi_lp8841_rtc {
|
||||
void *iomem;
|
||||
unsigned long state;
|
||||
};
|
||||
|
||||
static inline void
|
||||
setsck(struct spi_lp8841_rtc *data, int is_on)
|
||||
{
|
||||
if (is_on)
|
||||
data->state |= SPI_LP8841_RTC_CLK;
|
||||
else
|
||||
data->state &= ~SPI_LP8841_RTC_CLK;
|
||||
writeb(data->state, data->iomem);
|
||||
}
|
||||
|
||||
static inline void
|
||||
setmosi(struct spi_lp8841_rtc *data, int is_on)
|
||||
{
|
||||
if (is_on)
|
||||
data->state |= SPI_LP8841_RTC_MOSI;
|
||||
else
|
||||
data->state &= ~SPI_LP8841_RTC_MOSI;
|
||||
writeb(data->state, data->iomem);
|
||||
}
|
||||
|
||||
static inline int
|
||||
getmiso(struct spi_lp8841_rtc *data)
|
||||
{
|
||||
return ioread8(data->iomem) & SPI_LP8841_RTC_MISO;
|
||||
}
|
||||
|
||||
static inline u32
|
||||
bitbang_txrx_be_cpha0_lsb(struct spi_lp8841_rtc *data,
|
||||
unsigned usecs, unsigned cpol, unsigned flags,
|
||||
u32 word, u8 bits)
|
||||
{
|
||||
/* if (cpol == 0) this is SPI_MODE_0; else this is SPI_MODE_2 */
|
||||
|
||||
u32 shift = 32 - bits;
|
||||
/* clock starts at inactive polarity */
|
||||
for (; likely(bits); bits--) {
|
||||
|
||||
/* setup LSB (to slave) on leading edge */
|
||||
if ((flags & SPI_MASTER_NO_TX) == 0)
|
||||
setmosi(data, (word & 1));
|
||||
|
||||
usleep_range(usecs, usecs + 1); /* T(setup) */
|
||||
|
||||
/* sample LSB (from slave) on trailing edge */
|
||||
word >>= 1;
|
||||
if ((flags & SPI_MASTER_NO_RX) == 0)
|
||||
word |= (getmiso(data) << 31);
|
||||
|
||||
setsck(data, !cpol);
|
||||
usleep_range(usecs, usecs + 1);
|
||||
|
||||
setsck(data, cpol);
|
||||
}
|
||||
|
||||
word >>= shift;
|
||||
return word;
|
||||
}
|
||||
|
||||
static int
|
||||
spi_lp8841_rtc_transfer_one(struct spi_master *master,
|
||||
struct spi_device *spi,
|
||||
struct spi_transfer *t)
|
||||
{
|
||||
struct spi_lp8841_rtc *data = spi_master_get_devdata(master);
|
||||
unsigned count = t->len;
|
||||
const u8 *tx = t->tx_buf;
|
||||
u8 *rx = t->rx_buf;
|
||||
u8 word = 0;
|
||||
int ret = 0;
|
||||
|
||||
if (tx) {
|
||||
data->state &= ~SPI_LP8841_RTC_nWE;
|
||||
writeb(data->state, data->iomem);
|
||||
while (likely(count > 0)) {
|
||||
word = *tx++;
|
||||
bitbang_txrx_be_cpha0_lsb(data, 1, 0,
|
||||
SPI_MASTER_NO_RX, word, 8);
|
||||
count--;
|
||||
}
|
||||
} else if (rx) {
|
||||
data->state |= SPI_LP8841_RTC_nWE;
|
||||
writeb(data->state, data->iomem);
|
||||
while (likely(count > 0)) {
|
||||
word = bitbang_txrx_be_cpha0_lsb(data, 1, 0,
|
||||
SPI_MASTER_NO_TX, word, 8);
|
||||
*rx++ = word;
|
||||
count--;
|
||||
}
|
||||
} else {
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
spi_finalize_current_transfer(master);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
spi_lp8841_rtc_set_cs(struct spi_device *spi, bool enable)
|
||||
{
|
||||
struct spi_lp8841_rtc *data = spi_master_get_devdata(spi->master);
|
||||
|
||||
data->state = 0;
|
||||
writeb(data->state, data->iomem);
|
||||
if (enable) {
|
||||
usleep_range(4, 5);
|
||||
data->state |= SPI_LP8841_RTC_CE;
|
||||
writeb(data->state, data->iomem);
|
||||
usleep_range(4, 5);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
spi_lp8841_rtc_setup(struct spi_device *spi)
|
||||
{
|
||||
if ((spi->mode & SPI_CS_HIGH) == 0) {
|
||||
dev_err(&spi->dev, "unsupported active low chip select\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((spi->mode & SPI_LSB_FIRST) == 0) {
|
||||
dev_err(&spi->dev, "unsupported MSB first mode\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((spi->mode & SPI_3WIRE) == 0) {
|
||||
dev_err(&spi->dev, "unsupported wiring. 3 wires required\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id spi_lp8841_rtc_dt_ids[] = {
|
||||
{ .compatible = "icpdas,lp8841-spi-rtc" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, spi_lp8841_rtc_dt_ids);
|
||||
#endif
|
||||
|
||||
static int
|
||||
spi_lp8841_rtc_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
struct spi_master *master;
|
||||
struct spi_lp8841_rtc *data;
|
||||
void *iomem;
|
||||
|
||||
master = spi_alloc_master(&pdev->dev, sizeof(*data));
|
||||
if (!master)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, master);
|
||||
|
||||
master->flags = SPI_MASTER_HALF_DUPLEX;
|
||||
master->mode_bits = SPI_CS_HIGH | SPI_3WIRE | SPI_LSB_FIRST;
|
||||
|
||||
master->bus_num = pdev->id;
|
||||
master->num_chipselect = 1;
|
||||
master->setup = spi_lp8841_rtc_setup;
|
||||
master->set_cs = spi_lp8841_rtc_set_cs;
|
||||
master->transfer_one = spi_lp8841_rtc_transfer_one;
|
||||
master->bits_per_word_mask = SPI_BPW_MASK(8);
|
||||
#ifdef CONFIG_OF
|
||||
master->dev.of_node = pdev->dev.of_node;
|
||||
#endif
|
||||
|
||||
data = spi_master_get_devdata(master);
|
||||
|
||||
iomem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
data->iomem = devm_ioremap_resource(&pdev->dev, iomem);
|
||||
ret = PTR_ERR_OR_ZERO(data->iomem);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get IO address\n");
|
||||
goto err_put_master;
|
||||
}
|
||||
|
||||
/* register with the SPI framework */
|
||||
ret = devm_spi_register_master(&pdev->dev, master);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "cannot register spi master\n");
|
||||
goto err_put_master;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
|
||||
err_put_master:
|
||||
spi_master_put(master);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MODULE_ALIAS("platform:" DRIVER_NAME);
|
||||
|
||||
static struct platform_driver spi_lp8841_rtc_driver = {
|
||||
.driver = {
|
||||
.name = DRIVER_NAME,
|
||||
.of_match_table = of_match_ptr(spi_lp8841_rtc_dt_ids),
|
||||
},
|
||||
.probe = spi_lp8841_rtc_probe,
|
||||
};
|
||||
module_platform_driver(spi_lp8841_rtc_driver);
|
||||
|
||||
MODULE_DESCRIPTION("SPI master driver for ICP DAS LP-8841 RTC");
|
||||
MODULE_AUTHOR("Sergei Ianovich");
|
||||
MODULE_LICENSE("GPL");
|
|
@ -346,13 +346,6 @@ struct vendor_data {
|
|||
* @clk: outgoing clock "SPICLK" for the SPI bus
|
||||
* @master: SPI framework hookup
|
||||
* @master_info: controller-specific data from machine setup
|
||||
* @kworker: thread struct for message pump
|
||||
* @kworker_task: pointer to task for message pump kworker thread
|
||||
* @pump_messages: work struct for scheduling work to the message pump
|
||||
* @queue_lock: spinlock to syncronise access to message queue
|
||||
* @queue: message queue
|
||||
* @busy: message pump is busy
|
||||
* @running: message pump is running
|
||||
* @pump_transfers: Tasklet used in Interrupt Transfer mode
|
||||
* @cur_msg: Pointer to current spi_message being processed
|
||||
* @cur_transfer: Pointer to current spi_transfer
|
||||
|
|
|
@ -254,8 +254,8 @@ irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data)
|
|||
if (status & SSSR_ROR) {
|
||||
dev_err(&drv_data->pdev->dev, "FIFO overrun\n");
|
||||
|
||||
dmaengine_terminate_all(drv_data->rx_chan);
|
||||
dmaengine_terminate_all(drv_data->tx_chan);
|
||||
dmaengine_terminate_async(drv_data->rx_chan);
|
||||
dmaengine_terminate_async(drv_data->tx_chan);
|
||||
|
||||
pxa2xx_spi_dma_transfer_complete(drv_data, true);
|
||||
return IRQ_HANDLED;
|
||||
|
@ -331,13 +331,13 @@ int pxa2xx_spi_dma_setup(struct driver_data *drv_data)
|
|||
void pxa2xx_spi_dma_release(struct driver_data *drv_data)
|
||||
{
|
||||
if (drv_data->rx_chan) {
|
||||
dmaengine_terminate_all(drv_data->rx_chan);
|
||||
dmaengine_terminate_sync(drv_data->rx_chan);
|
||||
dma_release_channel(drv_data->rx_chan);
|
||||
sg_free_table(&drv_data->rx_sgt);
|
||||
drv_data->rx_chan = NULL;
|
||||
}
|
||||
if (drv_data->tx_chan) {
|
||||
dmaengine_terminate_all(drv_data->tx_chan);
|
||||
dmaengine_terminate_sync(drv_data->tx_chan);
|
||||
dma_release_channel(drv_data->tx_chan);
|
||||
sg_free_table(&drv_data->tx_sgt);
|
||||
drv_data->tx_chan = NULL;
|
||||
|
|
|
@ -19,6 +19,7 @@ enum {
|
|||
PORT_BSW1,
|
||||
PORT_BSW2,
|
||||
PORT_QUARK_X1000,
|
||||
PORT_LPT,
|
||||
};
|
||||
|
||||
struct pxa_spi_info {
|
||||
|
@ -42,6 +43,9 @@ static struct dw_dma_slave bsw1_rx_param = { .src_id = 7 };
|
|||
static struct dw_dma_slave bsw2_tx_param = { .dst_id = 8 };
|
||||
static struct dw_dma_slave bsw2_rx_param = { .src_id = 9 };
|
||||
|
||||
static struct dw_dma_slave lpt_tx_param = { .dst_id = 0 };
|
||||
static struct dw_dma_slave lpt_rx_param = { .src_id = 1 };
|
||||
|
||||
static bool lpss_dma_filter(struct dma_chan *chan, void *param)
|
||||
{
|
||||
struct dw_dma_slave *dws = param;
|
||||
|
@ -98,6 +102,14 @@ static struct pxa_spi_info spi_info_configs[] = {
|
|||
.num_chipselect = 1,
|
||||
.max_clk_rate = 50000000,
|
||||
},
|
||||
[PORT_LPT] = {
|
||||
.type = LPSS_LPT_SSP,
|
||||
.port_id = 0,
|
||||
.num_chipselect = 1,
|
||||
.max_clk_rate = 50000000,
|
||||
.tx_param = &lpt_tx_param,
|
||||
.rx_param = &lpt_rx_param,
|
||||
},
|
||||
};
|
||||
|
||||
static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
|
||||
|
@ -202,6 +214,7 @@ static const struct pci_device_id pxa2xx_spi_pci_devices[] = {
|
|||
{ PCI_VDEVICE(INTEL, 0x228e), PORT_BSW0 },
|
||||
{ PCI_VDEVICE(INTEL, 0x2290), PORT_BSW1 },
|
||||
{ PCI_VDEVICE(INTEL, 0x22ac), PORT_BSW2 },
|
||||
{ PCI_VDEVICE(INTEL, 0x9ce6), PORT_LPT },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, pxa2xx_spi_pci_devices);
|
||||
|
|
|
@ -65,8 +65,6 @@ MODULE_ALIAS("platform:pxa2xx-spi");
|
|||
#define LPSS_GENERAL_REG_RXTO_HOLDOFF_DISABLE BIT(24)
|
||||
#define LPSS_CS_CONTROL_SW_MODE BIT(0)
|
||||
#define LPSS_CS_CONTROL_CS_HIGH BIT(1)
|
||||
#define LPSS_CS_CONTROL_CS_SEL_SHIFT 8
|
||||
#define LPSS_CS_CONTROL_CS_SEL_MASK (3 << LPSS_CS_CONTROL_CS_SEL_SHIFT)
|
||||
#define LPSS_CAPS_CS_EN_SHIFT 9
|
||||
#define LPSS_CAPS_CS_EN_MASK (0xf << LPSS_CAPS_CS_EN_SHIFT)
|
||||
|
||||
|
@ -82,6 +80,10 @@ struct lpss_config {
|
|||
u32 rx_threshold;
|
||||
u32 tx_threshold_lo;
|
||||
u32 tx_threshold_hi;
|
||||
/* Chip select control */
|
||||
unsigned cs_sel_shift;
|
||||
unsigned cs_sel_mask;
|
||||
unsigned cs_num;
|
||||
};
|
||||
|
||||
/* Keep these sorted with enum pxa_ssp_type */
|
||||
|
@ -106,6 +108,19 @@ static const struct lpss_config lpss_platforms[] = {
|
|||
.tx_threshold_lo = 160,
|
||||
.tx_threshold_hi = 224,
|
||||
},
|
||||
{ /* LPSS_BSW_SSP */
|
||||
.offset = 0x400,
|
||||
.reg_general = 0x08,
|
||||
.reg_ssp = 0x0c,
|
||||
.reg_cs_ctrl = 0x18,
|
||||
.reg_capabilities = -1,
|
||||
.rx_threshold = 64,
|
||||
.tx_threshold_lo = 160,
|
||||
.tx_threshold_hi = 224,
|
||||
.cs_sel_shift = 2,
|
||||
.cs_sel_mask = 1 << 2,
|
||||
.cs_num = 2,
|
||||
},
|
||||
{ /* LPSS_SPT_SSP */
|
||||
.offset = 0x200,
|
||||
.reg_general = -1,
|
||||
|
@ -125,6 +140,8 @@ static const struct lpss_config lpss_platforms[] = {
|
|||
.rx_threshold = 1,
|
||||
.tx_threshold_lo = 16,
|
||||
.tx_threshold_hi = 48,
|
||||
.cs_sel_shift = 8,
|
||||
.cs_sel_mask = 3 << 8,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -139,6 +156,7 @@ static bool is_lpss_ssp(const struct driver_data *drv_data)
|
|||
switch (drv_data->ssp_type) {
|
||||
case LPSS_LPT_SSP:
|
||||
case LPSS_BYT_SSP:
|
||||
case LPSS_BSW_SSP:
|
||||
case LPSS_SPT_SSP:
|
||||
case LPSS_BXT_SSP:
|
||||
return true;
|
||||
|
@ -288,37 +306,50 @@ static void lpss_ssp_setup(struct driver_data *drv_data)
|
|||
}
|
||||
}
|
||||
|
||||
static void lpss_ssp_select_cs(struct driver_data *drv_data,
|
||||
const struct lpss_config *config)
|
||||
{
|
||||
u32 value, cs;
|
||||
|
||||
if (!config->cs_sel_mask)
|
||||
return;
|
||||
|
||||
value = __lpss_ssp_read_priv(drv_data, config->reg_cs_ctrl);
|
||||
|
||||
cs = drv_data->cur_msg->spi->chip_select;
|
||||
cs <<= config->cs_sel_shift;
|
||||
if (cs != (value & config->cs_sel_mask)) {
|
||||
/*
|
||||
* When switching another chip select output active the
|
||||
* output must be selected first and wait 2 ssp_clk cycles
|
||||
* before changing state to active. Otherwise a short
|
||||
* glitch will occur on the previous chip select since
|
||||
* output select is latched but state control is not.
|
||||
*/
|
||||
value &= ~config->cs_sel_mask;
|
||||
value |= cs;
|
||||
__lpss_ssp_write_priv(drv_data,
|
||||
config->reg_cs_ctrl, value);
|
||||
ndelay(1000000000 /
|
||||
(drv_data->master->max_speed_hz / 2));
|
||||
}
|
||||
}
|
||||
|
||||
static void lpss_ssp_cs_control(struct driver_data *drv_data, bool enable)
|
||||
{
|
||||
const struct lpss_config *config;
|
||||
u32 value, cs;
|
||||
u32 value;
|
||||
|
||||
config = lpss_get_config(drv_data);
|
||||
|
||||
if (enable)
|
||||
lpss_ssp_select_cs(drv_data, config);
|
||||
|
||||
value = __lpss_ssp_read_priv(drv_data, config->reg_cs_ctrl);
|
||||
if (enable) {
|
||||
cs = drv_data->cur_msg->spi->chip_select;
|
||||
cs <<= LPSS_CS_CONTROL_CS_SEL_SHIFT;
|
||||
if (cs != (value & LPSS_CS_CONTROL_CS_SEL_MASK)) {
|
||||
/*
|
||||
* When switching another chip select output active
|
||||
* the output must be selected first and wait 2 ssp_clk
|
||||
* cycles before changing state to active. Otherwise
|
||||
* a short glitch will occur on the previous chip
|
||||
* select since output select is latched but state
|
||||
* control is not.
|
||||
*/
|
||||
value &= ~LPSS_CS_CONTROL_CS_SEL_MASK;
|
||||
value |= cs;
|
||||
__lpss_ssp_write_priv(drv_data,
|
||||
config->reg_cs_ctrl, value);
|
||||
ndelay(1000000000 /
|
||||
(drv_data->master->max_speed_hz / 2));
|
||||
}
|
||||
if (enable)
|
||||
value &= ~LPSS_CS_CONTROL_CS_HIGH;
|
||||
} else {
|
||||
else
|
||||
value |= LPSS_CS_CONTROL_CS_HIGH;
|
||||
}
|
||||
__lpss_ssp_write_priv(drv_data, config->reg_cs_ctrl, value);
|
||||
}
|
||||
|
||||
|
@ -496,6 +527,7 @@ static void giveback(struct driver_data *drv_data)
|
|||
{
|
||||
struct spi_transfer* last_transfer;
|
||||
struct spi_message *msg;
|
||||
unsigned long timeout;
|
||||
|
||||
msg = drv_data->cur_msg;
|
||||
drv_data->cur_msg = NULL;
|
||||
|
@ -508,6 +540,12 @@ static void giveback(struct driver_data *drv_data)
|
|||
if (last_transfer->delay_usecs)
|
||||
udelay(last_transfer->delay_usecs);
|
||||
|
||||
/* Wait until SSP becomes idle before deasserting the CS */
|
||||
timeout = jiffies + msecs_to_jiffies(10);
|
||||
while (pxa2xx_spi_read(drv_data, SSSR) & SSSR_BSY &&
|
||||
!time_after(jiffies, timeout))
|
||||
cpu_relax();
|
||||
|
||||
/* Drop chip select UNLESS cs_change is true or we are returning
|
||||
* a message with an error, or next message is for another chip
|
||||
*/
|
||||
|
@ -572,7 +610,7 @@ static void int_error_stop(struct driver_data *drv_data, const char* msg)
|
|||
|
||||
static void int_transfer_complete(struct driver_data *drv_data)
|
||||
{
|
||||
/* Stop SSP */
|
||||
/* Clear and disable interrupts */
|
||||
write_SSSR_CS(drv_data, drv_data->clear_sr);
|
||||
reset_sccr1(drv_data);
|
||||
if (!pxa25x_ssp_comp(drv_data))
|
||||
|
@ -957,8 +995,6 @@ static void pump_transfers(unsigned long data)
|
|||
drv_data->tx_end = drv_data->tx + transfer->len;
|
||||
drv_data->rx = transfer->rx_buf;
|
||||
drv_data->rx_end = drv_data->rx + transfer->len;
|
||||
drv_data->rx_dma = transfer->rx_dma;
|
||||
drv_data->tx_dma = transfer->tx_dma;
|
||||
drv_data->len = transfer->len;
|
||||
drv_data->write = drv_data->tx ? chip->write : null_writer;
|
||||
drv_data->read = drv_data->rx ? chip->read : null_reader;
|
||||
|
@ -1001,19 +1037,6 @@ static void pump_transfers(unsigned long data)
|
|||
"pump_transfers: DMA burst size reduced to match bits_per_word\n");
|
||||
}
|
||||
|
||||
/* NOTE: PXA25x_SSP _could_ use external clocking ... */
|
||||
cr0 = pxa2xx_configure_sscr0(drv_data, clk_div, bits);
|
||||
if (!pxa25x_ssp_comp(drv_data))
|
||||
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
|
||||
drv_data->master->max_speed_hz
|
||||
/ (1 + ((cr0 & SSCR0_SCR(0xfff)) >> 8)),
|
||||
chip->enable_dma ? "DMA" : "PIO");
|
||||
else
|
||||
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
|
||||
drv_data->master->max_speed_hz / 2
|
||||
/ (1 + ((cr0 & SSCR0_SCR(0x0ff)) >> 8)),
|
||||
chip->enable_dma ? "DMA" : "PIO");
|
||||
|
||||
message->state = RUNNING_STATE;
|
||||
|
||||
drv_data->dma_mapped = 0;
|
||||
|
@ -1040,6 +1063,19 @@ static void pump_transfers(unsigned long data)
|
|||
write_SSSR_CS(drv_data, drv_data->clear_sr);
|
||||
}
|
||||
|
||||
/* NOTE: PXA25x_SSP _could_ use external clocking ... */
|
||||
cr0 = pxa2xx_configure_sscr0(drv_data, clk_div, bits);
|
||||
if (!pxa25x_ssp_comp(drv_data))
|
||||
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
|
||||
drv_data->master->max_speed_hz
|
||||
/ (1 + ((cr0 & SSCR0_SCR(0xfff)) >> 8)),
|
||||
drv_data->dma_mapped ? "DMA" : "PIO");
|
||||
else
|
||||
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
|
||||
drv_data->master->max_speed_hz / 2
|
||||
/ (1 + ((cr0 & SSCR0_SCR(0x0ff)) >> 8)),
|
||||
drv_data->dma_mapped ? "DMA" : "PIO");
|
||||
|
||||
if (is_lpss_ssp(drv_data)) {
|
||||
if ((pxa2xx_spi_read(drv_data, SSIRF) & 0xff)
|
||||
!= chip->lpss_rx_threshold)
|
||||
|
@ -1166,6 +1202,7 @@ static int setup(struct spi_device *spi)
|
|||
break;
|
||||
case LPSS_LPT_SSP:
|
||||
case LPSS_BYT_SSP:
|
||||
case LPSS_BSW_SSP:
|
||||
case LPSS_SPT_SSP:
|
||||
case LPSS_BXT_SSP:
|
||||
config = lpss_get_config(drv_data);
|
||||
|
@ -1313,7 +1350,7 @@ static const struct acpi_device_id pxa2xx_spi_acpi_match[] = {
|
|||
{ "INT3430", LPSS_LPT_SSP },
|
||||
{ "INT3431", LPSS_LPT_SSP },
|
||||
{ "80860F0E", LPSS_BYT_SSP },
|
||||
{ "8086228E", LPSS_BYT_SSP },
|
||||
{ "8086228E", LPSS_BSW_SSP },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(acpi, pxa2xx_spi_acpi_match);
|
||||
|
@ -1347,10 +1384,14 @@ static const struct pci_device_id pxa2xx_spi_pci_compound_match[] = {
|
|||
/* SPT-H */
|
||||
{ PCI_VDEVICE(INTEL, 0xa129), LPSS_SPT_SSP },
|
||||
{ PCI_VDEVICE(INTEL, 0xa12a), LPSS_SPT_SSP },
|
||||
/* BXT */
|
||||
/* BXT A-Step */
|
||||
{ PCI_VDEVICE(INTEL, 0x0ac2), LPSS_BXT_SSP },
|
||||
{ PCI_VDEVICE(INTEL, 0x0ac4), LPSS_BXT_SSP },
|
||||
{ PCI_VDEVICE(INTEL, 0x0ac6), LPSS_BXT_SSP },
|
||||
/* BXT B-Step */
|
||||
{ PCI_VDEVICE(INTEL, 0x1ac2), LPSS_BXT_SSP },
|
||||
{ PCI_VDEVICE(INTEL, 0x1ac4), LPSS_BXT_SSP },
|
||||
{ PCI_VDEVICE(INTEL, 0x1ac6), LPSS_BXT_SSP },
|
||||
/* APL */
|
||||
{ PCI_VDEVICE(INTEL, 0x5ac2), LPSS_BXT_SSP },
|
||||
{ PCI_VDEVICE(INTEL, 0x5ac4), LPSS_BXT_SSP },
|
||||
|
@ -1438,6 +1479,29 @@ pxa2xx_spi_init_pdata(struct platform_device *pdev)
|
|||
}
|
||||
#endif
|
||||
|
||||
static int pxa2xx_spi_fw_translate_cs(struct spi_master *master, unsigned cs)
|
||||
{
|
||||
struct driver_data *drv_data = spi_master_get_devdata(master);
|
||||
|
||||
if (has_acpi_companion(&drv_data->pdev->dev)) {
|
||||
switch (drv_data->ssp_type) {
|
||||
/*
|
||||
* For Atoms the ACPI DeviceSelection used by the Windows
|
||||
* driver starts from 1 instead of 0 so translate it here
|
||||
* to match what Linux expects.
|
||||
*/
|
||||
case LPSS_BYT_SSP:
|
||||
case LPSS_BSW_SSP:
|
||||
return cs - 1;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
static int pxa2xx_spi_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
|
@ -1490,6 +1554,7 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
|
|||
master->setup = setup;
|
||||
master->transfer_one_message = pxa2xx_spi_transfer_one_message;
|
||||
master->unprepare_transfer_hardware = pxa2xx_spi_unprepare_transfer;
|
||||
master->fw_translate_cs = pxa2xx_spi_fw_translate_cs;
|
||||
master->auto_runtime_pm = true;
|
||||
|
||||
drv_data->ssp_type = ssp->type;
|
||||
|
@ -1576,6 +1641,8 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
|
|||
tmp &= LPSS_CAPS_CS_EN_MASK;
|
||||
tmp >>= LPSS_CAPS_CS_EN_SHIFT;
|
||||
platform_info->num_chipselect = ffz(tmp);
|
||||
} else if (config->cs_num) {
|
||||
platform_info->num_chipselect = config->cs_num;
|
||||
}
|
||||
}
|
||||
master->num_chipselect = platform_info->num_chipselect;
|
||||
|
|
|
@ -69,8 +69,6 @@ struct driver_data {
|
|||
void *rx;
|
||||
void *rx_end;
|
||||
int dma_mapped;
|
||||
dma_addr_t rx_dma;
|
||||
dma_addr_t tx_dma;
|
||||
size_t rx_map_len;
|
||||
size_t tx_map_len;
|
||||
u8 n_bytes;
|
||||
|
@ -147,20 +145,9 @@ static inline void write_SSSR_CS(struct driver_data *drv_data, u32 val)
|
|||
extern int pxa2xx_spi_flush(struct driver_data *drv_data);
|
||||
extern void *pxa2xx_spi_next_transfer(struct driver_data *drv_data);
|
||||
|
||||
/*
|
||||
* Select the right DMA implementation.
|
||||
*/
|
||||
#if defined(CONFIG_SPI_PXA2XX_DMA)
|
||||
#define SPI_PXA2XX_USE_DMA 1
|
||||
#define MAX_DMA_LEN SZ_64K
|
||||
#define DEFAULT_DMA_CR1 (SSCR1_TSRE | SSCR1_RSRE | SSCR1_TRAIL)
|
||||
#else
|
||||
#undef SPI_PXA2XX_USE_DMA
|
||||
#define MAX_DMA_LEN 0
|
||||
#define DEFAULT_DMA_CR1 0
|
||||
#endif
|
||||
|
||||
#ifdef SPI_PXA2XX_USE_DMA
|
||||
extern bool pxa2xx_spi_dma_is_possible(size_t len);
|
||||
extern int pxa2xx_spi_map_dma_buffers(struct driver_data *drv_data);
|
||||
extern irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data);
|
||||
|
@ -173,29 +160,5 @@ extern int pxa2xx_spi_set_dma_burst_and_threshold(struct chip_data *chip,
|
|||
u8 bits_per_word,
|
||||
u32 *burst_code,
|
||||
u32 *threshold);
|
||||
#else
|
||||
static inline bool pxa2xx_spi_dma_is_possible(size_t len) { return false; }
|
||||
static inline int pxa2xx_spi_map_dma_buffers(struct driver_data *drv_data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#define pxa2xx_spi_dma_transfer NULL
|
||||
static inline void pxa2xx_spi_dma_prepare(struct driver_data *drv_data,
|
||||
u32 dma_burst) {}
|
||||
static inline void pxa2xx_spi_dma_start(struct driver_data *drv_data) {}
|
||||
static inline int pxa2xx_spi_dma_setup(struct driver_data *drv_data)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
static inline void pxa2xx_spi_dma_release(struct driver_data *drv_data) {}
|
||||
static inline int pxa2xx_spi_set_dma_burst_and_threshold(struct chip_data *chip,
|
||||
struct spi_device *spi,
|
||||
u8 bits_per_word,
|
||||
u32 *burst_code,
|
||||
u32 *threshold)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SPI_PXA2XX_H */
|
||||
|
|
|
@ -144,6 +144,8 @@ SPI_STATISTICS_TRANSFER_BYTES_HISTO(14, "16384-32767");
|
|||
SPI_STATISTICS_TRANSFER_BYTES_HISTO(15, "32768-65535");
|
||||
SPI_STATISTICS_TRANSFER_BYTES_HISTO(16, "65536+");
|
||||
|
||||
SPI_STATISTICS_SHOW(transfers_split_maxsize, "%lu");
|
||||
|
||||
static struct attribute *spi_dev_attrs[] = {
|
||||
&dev_attr_modalias.attr,
|
||||
NULL,
|
||||
|
@ -181,6 +183,7 @@ static struct attribute *spi_device_statistics_attrs[] = {
|
|||
&dev_attr_spi_device_transfer_bytes_histo14.attr,
|
||||
&dev_attr_spi_device_transfer_bytes_histo15.attr,
|
||||
&dev_attr_spi_device_transfer_bytes_histo16.attr,
|
||||
&dev_attr_spi_device_transfers_split_maxsize.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -223,6 +226,7 @@ static struct attribute *spi_master_statistics_attrs[] = {
|
|||
&dev_attr_spi_master_transfer_bytes_histo14.attr,
|
||||
&dev_attr_spi_master_transfer_bytes_histo15.attr,
|
||||
&dev_attr_spi_master_transfer_bytes_histo16.attr,
|
||||
&dev_attr_spi_master_transfers_split_maxsize.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -1024,6 +1028,8 @@ out:
|
|||
if (msg->status && master->handle_err)
|
||||
master->handle_err(master, msg);
|
||||
|
||||
spi_res_release(master, msg);
|
||||
|
||||
spi_finalize_current_message(master);
|
||||
|
||||
return ret;
|
||||
|
@ -2043,6 +2049,336 @@ struct spi_master *spi_busnum_to_master(u16 bus_num)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(spi_busnum_to_master);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Core methods for SPI resource management */
|
||||
|
||||
/**
|
||||
* spi_res_alloc - allocate a spi resource that is life-cycle managed
|
||||
* during the processing of a spi_message while using
|
||||
* spi_transfer_one
|
||||
* @spi: the spi device for which we allocate memory
|
||||
* @release: the release code to execute for this resource
|
||||
* @size: size to alloc and return
|
||||
* @gfp: GFP allocation flags
|
||||
*
|
||||
* Return: the pointer to the allocated data
|
||||
*
|
||||
* This may get enhanced in the future to allocate from a memory pool
|
||||
* of the @spi_device or @spi_master to avoid repeated allocations.
|
||||
*/
|
||||
void *spi_res_alloc(struct spi_device *spi,
|
||||
spi_res_release_t release,
|
||||
size_t size, gfp_t gfp)
|
||||
{
|
||||
struct spi_res *sres;
|
||||
|
||||
sres = kzalloc(sizeof(*sres) + size, gfp);
|
||||
if (!sres)
|
||||
return NULL;
|
||||
|
||||
INIT_LIST_HEAD(&sres->entry);
|
||||
sres->release = release;
|
||||
|
||||
return sres->data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_res_alloc);
|
||||
|
||||
/**
|
||||
* spi_res_free - free an spi resource
|
||||
* @res: pointer to the custom data of a resource
|
||||
*
|
||||
*/
|
||||
void spi_res_free(void *res)
|
||||
{
|
||||
struct spi_res *sres = container_of(res, struct spi_res, data);
|
||||
|
||||
if (!res)
|
||||
return;
|
||||
|
||||
WARN_ON(!list_empty(&sres->entry));
|
||||
kfree(sres);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_res_free);
|
||||
|
||||
/**
|
||||
* spi_res_add - add a spi_res to the spi_message
|
||||
* @message: the spi message
|
||||
* @res: the spi_resource
|
||||
*/
|
||||
void spi_res_add(struct spi_message *message, void *res)
|
||||
{
|
||||
struct spi_res *sres = container_of(res, struct spi_res, data);
|
||||
|
||||
WARN_ON(!list_empty(&sres->entry));
|
||||
list_add_tail(&sres->entry, &message->resources);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_res_add);
|
||||
|
||||
/**
|
||||
* spi_res_release - release all spi resources for this message
|
||||
* @master: the @spi_master
|
||||
* @message: the @spi_message
|
||||
*/
|
||||
void spi_res_release(struct spi_master *master,
|
||||
struct spi_message *message)
|
||||
{
|
||||
struct spi_res *res;
|
||||
|
||||
while (!list_empty(&message->resources)) {
|
||||
res = list_last_entry(&message->resources,
|
||||
struct spi_res, entry);
|
||||
|
||||
if (res->release)
|
||||
res->release(master, message, res->data);
|
||||
|
||||
list_del(&res->entry);
|
||||
|
||||
kfree(res);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_res_release);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
/* Core methods for spi_message alterations */
|
||||
|
||||
static void __spi_replace_transfers_release(struct spi_master *master,
|
||||
struct spi_message *msg,
|
||||
void *res)
|
||||
{
|
||||
struct spi_replaced_transfers *rxfer = res;
|
||||
size_t i;
|
||||
|
||||
/* call extra callback if requested */
|
||||
if (rxfer->release)
|
||||
rxfer->release(master, msg, res);
|
||||
|
||||
/* insert replaced transfers back into the message */
|
||||
list_splice(&rxfer->replaced_transfers, rxfer->replaced_after);
|
||||
|
||||
/* remove the formerly inserted entries */
|
||||
for (i = 0; i < rxfer->inserted; i++)
|
||||
list_del(&rxfer->inserted_transfers[i].transfer_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_replace_transfers - replace transfers with several transfers
|
||||
* and register change with spi_message.resources
|
||||
* @msg: the spi_message we work upon
|
||||
* @xfer_first: the first spi_transfer we want to replace
|
||||
* @remove: number of transfers to remove
|
||||
* @insert: the number of transfers we want to insert instead
|
||||
* @release: extra release code necessary in some circumstances
|
||||
* @extradatasize: extra data to allocate (with alignment guarantees
|
||||
* of struct @spi_transfer)
|
||||
* @gfp: gfp flags
|
||||
*
|
||||
* Returns: pointer to @spi_replaced_transfers,
|
||||
* PTR_ERR(...) in case of errors.
|
||||
*/
|
||||
struct spi_replaced_transfers *spi_replace_transfers(
|
||||
struct spi_message *msg,
|
||||
struct spi_transfer *xfer_first,
|
||||
size_t remove,
|
||||
size_t insert,
|
||||
spi_replaced_release_t release,
|
||||
size_t extradatasize,
|
||||
gfp_t gfp)
|
||||
{
|
||||
struct spi_replaced_transfers *rxfer;
|
||||
struct spi_transfer *xfer;
|
||||
size_t i;
|
||||
|
||||
/* allocate the structure using spi_res */
|
||||
rxfer = spi_res_alloc(msg->spi, __spi_replace_transfers_release,
|
||||
insert * sizeof(struct spi_transfer)
|
||||
+ sizeof(struct spi_replaced_transfers)
|
||||
+ extradatasize,
|
||||
gfp);
|
||||
if (!rxfer)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
/* the release code to invoke before running the generic release */
|
||||
rxfer->release = release;
|
||||
|
||||
/* assign extradata */
|
||||
if (extradatasize)
|
||||
rxfer->extradata =
|
||||
&rxfer->inserted_transfers[insert];
|
||||
|
||||
/* init the replaced_transfers list */
|
||||
INIT_LIST_HEAD(&rxfer->replaced_transfers);
|
||||
|
||||
/* assign the list_entry after which we should reinsert
|
||||
* the @replaced_transfers - it may be spi_message.messages!
|
||||
*/
|
||||
rxfer->replaced_after = xfer_first->transfer_list.prev;
|
||||
|
||||
/* remove the requested number of transfers */
|
||||
for (i = 0; i < remove; i++) {
|
||||
/* if the entry after replaced_after it is msg->transfers
|
||||
* then we have been requested to remove more transfers
|
||||
* than are in the list
|
||||
*/
|
||||
if (rxfer->replaced_after->next == &msg->transfers) {
|
||||
dev_err(&msg->spi->dev,
|
||||
"requested to remove more spi_transfers than are available\n");
|
||||
/* insert replaced transfers back into the message */
|
||||
list_splice(&rxfer->replaced_transfers,
|
||||
rxfer->replaced_after);
|
||||
|
||||
/* free the spi_replace_transfer structure */
|
||||
spi_res_free(rxfer);
|
||||
|
||||
/* and return with an error */
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/* remove the entry after replaced_after from list of
|
||||
* transfers and add it to list of replaced_transfers
|
||||
*/
|
||||
list_move_tail(rxfer->replaced_after->next,
|
||||
&rxfer->replaced_transfers);
|
||||
}
|
||||
|
||||
/* create copy of the given xfer with identical settings
|
||||
* based on the first transfer to get removed
|
||||
*/
|
||||
for (i = 0; i < insert; i++) {
|
||||
/* we need to run in reverse order */
|
||||
xfer = &rxfer->inserted_transfers[insert - 1 - i];
|
||||
|
||||
/* copy all spi_transfer data */
|
||||
memcpy(xfer, xfer_first, sizeof(*xfer));
|
||||
|
||||
/* add to list */
|
||||
list_add(&xfer->transfer_list, rxfer->replaced_after);
|
||||
|
||||
/* clear cs_change and delay_usecs for all but the last */
|
||||
if (i) {
|
||||
xfer->cs_change = false;
|
||||
xfer->delay_usecs = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* set up inserted */
|
||||
rxfer->inserted = insert;
|
||||
|
||||
/* and register it with spi_res/spi_message */
|
||||
spi_res_add(msg, rxfer);
|
||||
|
||||
return rxfer;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_replace_transfers);
|
||||
|
||||
static int __spi_split_transfer_maxsize(struct spi_master *master,
|
||||
struct spi_message *msg,
|
||||
struct spi_transfer **xferp,
|
||||
size_t maxsize,
|
||||
gfp_t gfp)
|
||||
{
|
||||
struct spi_transfer *xfer = *xferp, *xfers;
|
||||
struct spi_replaced_transfers *srt;
|
||||
size_t offset;
|
||||
size_t count, i;
|
||||
|
||||
/* warn once about this fact that we are splitting a transfer */
|
||||
dev_warn_once(&msg->spi->dev,
|
||||
"spi_transfer of length %i exceed max length of %zu - needed to split transfers\n",
|
||||
xfer->len, maxsize);
|
||||
|
||||
/* calculate how many we have to replace */
|
||||
count = DIV_ROUND_UP(xfer->len, maxsize);
|
||||
|
||||
/* create replacement */
|
||||
srt = spi_replace_transfers(msg, xfer, 1, count, NULL, 0, gfp);
|
||||
if (IS_ERR(srt))
|
||||
return PTR_ERR(srt);
|
||||
xfers = srt->inserted_transfers;
|
||||
|
||||
/* now handle each of those newly inserted spi_transfers
|
||||
* note that the replacements spi_transfers all are preset
|
||||
* to the same values as *xferp, so tx_buf, rx_buf and len
|
||||
* are all identical (as well as most others)
|
||||
* so we just have to fix up len and the pointers.
|
||||
*
|
||||
* this also includes support for the depreciated
|
||||
* spi_message.is_dma_mapped interface
|
||||
*/
|
||||
|
||||
/* the first transfer just needs the length modified, so we
|
||||
* run it outside the loop
|
||||
*/
|
||||
xfers[0].len = min_t(size_t, maxsize, xfer[0].len);
|
||||
|
||||
/* all the others need rx_buf/tx_buf also set */
|
||||
for (i = 1, offset = maxsize; i < count; offset += maxsize, i++) {
|
||||
/* update rx_buf, tx_buf and dma */
|
||||
if (xfers[i].rx_buf)
|
||||
xfers[i].rx_buf += offset;
|
||||
if (xfers[i].rx_dma)
|
||||
xfers[i].rx_dma += offset;
|
||||
if (xfers[i].tx_buf)
|
||||
xfers[i].tx_buf += offset;
|
||||
if (xfers[i].tx_dma)
|
||||
xfers[i].tx_dma += offset;
|
||||
|
||||
/* update length */
|
||||
xfers[i].len = min(maxsize, xfers[i].len - offset);
|
||||
}
|
||||
|
||||
/* we set up xferp to the last entry we have inserted,
|
||||
* so that we skip those already split transfers
|
||||
*/
|
||||
*xferp = &xfers[count - 1];
|
||||
|
||||
/* increment statistics counters */
|
||||
SPI_STATISTICS_INCREMENT_FIELD(&master->statistics,
|
||||
transfers_split_maxsize);
|
||||
SPI_STATISTICS_INCREMENT_FIELD(&msg->spi->statistics,
|
||||
transfers_split_maxsize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* spi_split_tranfers_maxsize - split spi transfers into multiple transfers
|
||||
* when an individual transfer exceeds a
|
||||
* certain size
|
||||
* @master: the @spi_master for this transfer
|
||||
* @msg: the @spi_message to transform
|
||||
* @maxsize: the maximum when to apply this
|
||||
* @gfp: GFP allocation flags
|
||||
*
|
||||
* Return: status of transformation
|
||||
*/
|
||||
int spi_split_transfers_maxsize(struct spi_master *master,
|
||||
struct spi_message *msg,
|
||||
size_t maxsize,
|
||||
gfp_t gfp)
|
||||
{
|
||||
struct spi_transfer *xfer;
|
||||
int ret;
|
||||
|
||||
/* iterate over the transfer_list,
|
||||
* but note that xfer is advanced to the last transfer inserted
|
||||
* to avoid checking sizes again unnecessarily (also xfer does
|
||||
* potentiall belong to a different list by the time the
|
||||
* replacement has happened
|
||||
*/
|
||||
list_for_each_entry(xfer, &msg->transfers, transfer_list) {
|
||||
if (xfer->len > maxsize) {
|
||||
ret = __spi_split_transfer_maxsize(
|
||||
master, msg, &xfer, maxsize, gfp);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(spi_split_transfers_maxsize);
|
||||
|
||||
/*-------------------------------------------------------------------------*/
|
||||
|
||||
|
|
|
@ -197,6 +197,7 @@ enum pxa_ssp_type {
|
|||
QUARK_X1000_SSP,
|
||||
LPSS_LPT_SSP, /* Keep LPSS types sorted with lpss_platforms[] */
|
||||
LPSS_BYT_SSP,
|
||||
LPSS_BSW_SSP,
|
||||
LPSS_SPT_SSP,
|
||||
LPSS_BXT_SSP,
|
||||
};
|
||||
|
|
|
@ -54,6 +54,10 @@ extern struct bus_type spi_bus_type;
|
|||
*
|
||||
* @transfer_bytes_histo:
|
||||
* transfer bytes histogramm
|
||||
*
|
||||
* @transfers_split_maxsize:
|
||||
* number of transfers that have been split because of
|
||||
* maxsize limit
|
||||
*/
|
||||
struct spi_statistics {
|
||||
spinlock_t lock; /* lock for the whole structure */
|
||||
|
@ -73,6 +77,8 @@ struct spi_statistics {
|
|||
|
||||
#define SPI_STATISTICS_HISTO_SIZE 17
|
||||
unsigned long transfer_bytes_histo[SPI_STATISTICS_HISTO_SIZE];
|
||||
|
||||
unsigned long transfers_split_maxsize;
|
||||
};
|
||||
|
||||
void spi_statistics_add_transfer_stats(struct spi_statistics *stats,
|
||||
|
@ -594,6 +600,37 @@ extern void spi_unregister_master(struct spi_master *master);
|
|||
|
||||
extern struct spi_master *spi_busnum_to_master(u16 busnum);
|
||||
|
||||
/*
|
||||
* SPI resource management while processing a SPI message
|
||||
*/
|
||||
|
||||
/**
|
||||
* struct spi_res - spi resource management structure
|
||||
* @entry: list entry
|
||||
* @release: release code called prior to freeing this resource
|
||||
* @data: extra data allocated for the specific use-case
|
||||
*
|
||||
* this is based on ideas from devres, but focused on life-cycle
|
||||
* management during spi_message processing
|
||||
*/
|
||||
typedef void (*spi_res_release_t)(struct spi_master *master,
|
||||
struct spi_message *msg,
|
||||
void *res);
|
||||
struct spi_res {
|
||||
struct list_head entry;
|
||||
spi_res_release_t release;
|
||||
unsigned long long data[]; /* guarantee ull alignment */
|
||||
};
|
||||
|
||||
extern void *spi_res_alloc(struct spi_device *spi,
|
||||
spi_res_release_t release,
|
||||
size_t size, gfp_t gfp);
|
||||
extern void spi_res_add(struct spi_message *message, void *res);
|
||||
extern void spi_res_free(void *res);
|
||||
|
||||
extern void spi_res_release(struct spi_master *master,
|
||||
struct spi_message *message);
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/*
|
||||
|
@ -732,6 +769,7 @@ struct spi_transfer {
|
|||
* @status: zero for success, else negative errno
|
||||
* @queue: for use by whichever driver currently owns the message
|
||||
* @state: for use by whichever driver currently owns the message
|
||||
* @resources: for resource management when the spi message is processed
|
||||
*
|
||||
* A @spi_message is used to execute an atomic sequence of data transfers,
|
||||
* each represented by a struct spi_transfer. The sequence is "atomic"
|
||||
|
@ -778,11 +816,15 @@ struct spi_message {
|
|||
*/
|
||||
struct list_head queue;
|
||||
void *state;
|
||||
|
||||
/* list of spi_res reources when the spi message is processed */
|
||||
struct list_head resources;
|
||||
};
|
||||
|
||||
static inline void spi_message_init_no_memset(struct spi_message *m)
|
||||
{
|
||||
INIT_LIST_HEAD(&m->transfers);
|
||||
INIT_LIST_HEAD(&m->resources);
|
||||
}
|
||||
|
||||
static inline void spi_message_init(struct spi_message *m)
|
||||
|
@ -866,6 +908,60 @@ spi_max_transfer_size(struct spi_device *spi)
|
|||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/* SPI transfer replacement methods which make use of spi_res */
|
||||
|
||||
struct spi_replaced_transfers;
|
||||
typedef void (*spi_replaced_release_t)(struct spi_master *master,
|
||||
struct spi_message *msg,
|
||||
struct spi_replaced_transfers *res);
|
||||
/**
|
||||
* struct spi_replaced_transfers - structure describing the spi_transfer
|
||||
* replacements that have occurred
|
||||
* so that they can get reverted
|
||||
* @release: some extra release code to get executed prior to
|
||||
* relasing this structure
|
||||
* @extradata: pointer to some extra data if requested or NULL
|
||||
* @replaced_transfers: transfers that have been replaced and which need
|
||||
* to get restored
|
||||
* @replaced_after: the transfer after which the @replaced_transfers
|
||||
* are to get re-inserted
|
||||
* @inserted: number of transfers inserted
|
||||
* @inserted_transfers: array of spi_transfers of array-size @inserted,
|
||||
* that have been replacing replaced_transfers
|
||||
*
|
||||
* note: that @extradata will point to @inserted_transfers[@inserted]
|
||||
* if some extra allocation is requested, so alignment will be the same
|
||||
* as for spi_transfers
|
||||
*/
|
||||
struct spi_replaced_transfers {
|
||||
spi_replaced_release_t release;
|
||||
void *extradata;
|
||||
struct list_head replaced_transfers;
|
||||
struct list_head *replaced_after;
|
||||
size_t inserted;
|
||||
struct spi_transfer inserted_transfers[];
|
||||
};
|
||||
|
||||
extern struct spi_replaced_transfers *spi_replace_transfers(
|
||||
struct spi_message *msg,
|
||||
struct spi_transfer *xfer_first,
|
||||
size_t remove,
|
||||
size_t insert,
|
||||
spi_replaced_release_t release,
|
||||
size_t extradatasize,
|
||||
gfp_t gfp);
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/* SPI transfer transformation methods */
|
||||
|
||||
extern int spi_split_transfers_maxsize(struct spi_master *master,
|
||||
struct spi_message *msg,
|
||||
size_t maxsize,
|
||||
gfp_t gfp);
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/* All these synchronous SPI transfer routines are utilities layered
|
||||
* over the core async transfer primitive. Here, "synchronous" means
|
||||
* they will sleep uninterruptibly until the async transfer completes.
|
||||
|
|
Loading…
Reference in New Issue