Commit 9a4506b6 authored by Mark Brown's avatar Mark Brown

Merge remote-tracking branches 'spi/topic/pxa2xx', 'spi/topic/rockchip',...

Merge remote-tracking branches 'spi/topic/pxa2xx', 'spi/topic/rockchip', 'spi/topic/s3c64xx', 'spi/topic/sh' and 'spi/topic/sh-msiof' into spi-next
...@@ -6,10 +6,13 @@ and display controllers using the SPI communication interface. ...@@ -6,10 +6,13 @@ and display controllers using the SPI communication interface.
Required Properties: Required Properties:
- compatible: should be one of the following. - compatible: should be one of the following.
"rockchip,rk3066-spi" for rk3066. "rockchip,rk3036-spi" for rk3036 SoCS.
"rockchip,rk3188-spi", "rockchip,rk3066-spi" for rk3188. "rockchip,rk3066-spi" for rk3066 SoCs.
"rockchip,rk3288-spi", "rockchip,rk3066-spi" for rk3288. "rockchip,rk3188-spi" for rk3188 SoCs.
"rockchip,rk3399-spi", "rockchip,rk3066-spi" for rk3399. "rockchip,rk3228-spi" for rk3228 SoCS.
"rockchip,rk3288-spi" for rk3288 SoCs.
"rockchip,rk3368-spi" for rk3368 SoCs.
"rockchip,rk3399-spi" for rk3399 SoCs.
- reg: physical base address of the controller and length of memory mapped - reg: physical base address of the controller and length of memory mapped
region. region.
- interrupts: The interrupt number to the cpu. The interrupt specifier format - interrupts: The interrupt number to the cpu. The interrupt specifier format
......
...@@ -9,7 +9,8 @@ Required SoC Specific Properties: ...@@ -9,7 +9,8 @@ Required SoC Specific Properties:
- samsung,s3c2443-spi: for s3c2443, s3c2416 and s3c2450 platforms - samsung,s3c2443-spi: for s3c2443, s3c2416 and s3c2450 platforms
- samsung,s3c6410-spi: for s3c6410 platforms - samsung,s3c6410-spi: for s3c6410 platforms
- samsung,s5pv210-spi: for s5pv210 and s5pc110 platforms - samsung,s5pv210-spi: for s5pv210 and s5pc110 platforms
- samsung,exynos7-spi: for exynos7 platforms - samsung,exynos5433-spi: for exynos5433 compatible controllers
- samsung,exynos7-spi: for exynos7 platforms <DEPRECATED>
- reg: physical base address of the controller and length of memory mapped - reg: physical base address of the controller and length of memory mapped
region. region.
...@@ -23,6 +24,15 @@ Required SoC Specific Properties: ...@@ -23,6 +24,15 @@ Required SoC Specific Properties:
- dma-names: Names for the dma channels. There must be at least one channel - dma-names: Names for the dma channels. There must be at least one channel
named "tx" for transmit and named "rx" for receive. named "tx" for transmit and named "rx" for receive.
- clocks: specifies the clock IDs provided to the SPI controller; they are
required for interacting with the controller itself, for synchronizing the bus
and as I/O clock (the latter is required by exynos5433 and exynos7).
- clock-names: string names of the clocks in the 'clocks' property; for all the
the devices the names must be "spi", "spi_busclkN" (where N is determined by
"samsung,spi-src-clk"), while Exynos5433 should specify a third clock
"spi_ioclk" for the I/O clock.
Required Board Specific Properties: Required Board Specific Properties:
- #address-cells: should be 1. - #address-cells: should be 1.
...@@ -40,6 +50,9 @@ Optional Board Specific Properties: ...@@ -40,6 +50,9 @@ Optional Board Specific Properties:
- cs-gpios: should specify GPIOs used for chipselects (see spi-bus.txt) - cs-gpios: should specify GPIOs used for chipselects (see spi-bus.txt)
- no-cs-readback: the CS line is disconnected, therefore the device should not
operate based on CS signalling.
SPI Controller specific data in SPI slave nodes: SPI Controller specific data in SPI slave nodes:
- The spi slave nodes should provide the following information which is required - The spi slave nodes should provide the following information which is required
......
...@@ -20,79 +20,6 @@ ...@@ -20,79 +20,6 @@
#include "spi-pxa2xx.h" #include "spi-pxa2xx.h"
static int pxa2xx_spi_map_dma_buffer(struct driver_data *drv_data,
enum dma_data_direction dir)
{
int i, nents, len = drv_data->len;
struct scatterlist *sg;
struct device *dmadev;
struct sg_table *sgt;
void *buf, *pbuf;
if (dir == DMA_TO_DEVICE) {
dmadev = drv_data->tx_chan->device->dev;
sgt = &drv_data->tx_sgt;
buf = drv_data->tx;
} else {
dmadev = drv_data->rx_chan->device->dev;
sgt = &drv_data->rx_sgt;
buf = drv_data->rx;
}
nents = DIV_ROUND_UP(len, SZ_2K);
if (nents != sgt->nents) {
int ret;
sg_free_table(sgt);
ret = sg_alloc_table(sgt, nents, GFP_ATOMIC);
if (ret)
return ret;
}
pbuf = buf;
for_each_sg(sgt->sgl, sg, sgt->nents, i) {
size_t bytes = min_t(size_t, len, SZ_2K);
sg_set_buf(sg, pbuf, bytes);
pbuf += bytes;
len -= bytes;
}
nents = dma_map_sg(dmadev, sgt->sgl, sgt->nents, dir);
if (!nents)
return -ENOMEM;
return nents;
}
static void pxa2xx_spi_unmap_dma_buffer(struct driver_data *drv_data,
enum dma_data_direction dir)
{
struct device *dmadev;
struct sg_table *sgt;
if (dir == DMA_TO_DEVICE) {
dmadev = drv_data->tx_chan->device->dev;
sgt = &drv_data->tx_sgt;
} else {
dmadev = drv_data->rx_chan->device->dev;
sgt = &drv_data->rx_sgt;
}
dma_unmap_sg(dmadev, sgt->sgl, sgt->nents, dir);
}
static void pxa2xx_spi_unmap_dma_buffers(struct driver_data *drv_data)
{
if (!drv_data->dma_mapped)
return;
pxa2xx_spi_unmap_dma_buffer(drv_data, DMA_FROM_DEVICE);
pxa2xx_spi_unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
drv_data->dma_mapped = 0;
}
static void pxa2xx_spi_dma_transfer_complete(struct driver_data *drv_data, static void pxa2xx_spi_dma_transfer_complete(struct driver_data *drv_data,
bool error) bool error)
{ {
...@@ -125,8 +52,6 @@ static void pxa2xx_spi_dma_transfer_complete(struct driver_data *drv_data, ...@@ -125,8 +52,6 @@ static void pxa2xx_spi_dma_transfer_complete(struct driver_data *drv_data,
pxa2xx_spi_write(drv_data, SSTO, 0); pxa2xx_spi_write(drv_data, SSTO, 0);
if (!error) { if (!error) {
pxa2xx_spi_unmap_dma_buffers(drv_data);
msg->actual_length += drv_data->len; msg->actual_length += drv_data->len;
msg->state = pxa2xx_spi_next_transfer(drv_data); msg->state = pxa2xx_spi_next_transfer(drv_data);
} else { } else {
...@@ -152,11 +77,12 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data, ...@@ -152,11 +77,12 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data,
enum dma_transfer_direction dir) enum dma_transfer_direction dir)
{ {
struct chip_data *chip = drv_data->cur_chip; struct chip_data *chip = drv_data->cur_chip;
struct spi_transfer *xfer = drv_data->cur_transfer;
enum dma_slave_buswidth width; enum dma_slave_buswidth width;
struct dma_slave_config cfg; struct dma_slave_config cfg;
struct dma_chan *chan; struct dma_chan *chan;
struct sg_table *sgt; struct sg_table *sgt;
int nents, ret; int ret;
switch (drv_data->n_bytes) { switch (drv_data->n_bytes) {
case 1: case 1:
...@@ -178,17 +104,15 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data, ...@@ -178,17 +104,15 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data,
cfg.dst_addr_width = width; cfg.dst_addr_width = width;
cfg.dst_maxburst = chip->dma_burst_size; cfg.dst_maxburst = chip->dma_burst_size;
sgt = &drv_data->tx_sgt; sgt = &xfer->tx_sg;
nents = drv_data->tx_nents; chan = drv_data->master->dma_tx;
chan = drv_data->tx_chan;
} else { } else {
cfg.src_addr = drv_data->ssdr_physical; cfg.src_addr = drv_data->ssdr_physical;
cfg.src_addr_width = width; cfg.src_addr_width = width;
cfg.src_maxburst = chip->dma_burst_size; cfg.src_maxburst = chip->dma_burst_size;
sgt = &drv_data->rx_sgt; sgt = &xfer->rx_sg;
nents = drv_data->rx_nents; chan = drv_data->master->dma_rx;
chan = drv_data->rx_chan;
} }
ret = dmaengine_slave_config(chan, &cfg); ret = dmaengine_slave_config(chan, &cfg);
...@@ -197,46 +121,10 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data, ...@@ -197,46 +121,10 @@ pxa2xx_spi_dma_prepare_one(struct driver_data *drv_data,
return NULL; return NULL;
} }
return dmaengine_prep_slave_sg(chan, sgt->sgl, nents, dir, return dmaengine_prep_slave_sg(chan, sgt->sgl, sgt->nents, dir,
DMA_PREP_INTERRUPT | DMA_CTRL_ACK); DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
} }
bool pxa2xx_spi_dma_is_possible(size_t len)
{
return len <= MAX_DMA_LEN;
}
int pxa2xx_spi_map_dma_buffers(struct driver_data *drv_data)
{
const struct chip_data *chip = drv_data->cur_chip;
int ret;
if (!chip->enable_dma)
return 0;
/* Don't bother with DMA if we can't do even a single burst */
if (drv_data->len < chip->dma_burst_size)
return 0;
ret = pxa2xx_spi_map_dma_buffer(drv_data, DMA_TO_DEVICE);
if (ret <= 0) {
dev_warn(&drv_data->pdev->dev, "failed to DMA map TX\n");
return 0;
}
drv_data->tx_nents = ret;
ret = pxa2xx_spi_map_dma_buffer(drv_data, DMA_FROM_DEVICE);
if (ret <= 0) {
pxa2xx_spi_unmap_dma_buffer(drv_data, DMA_TO_DEVICE);
dev_warn(&drv_data->pdev->dev, "failed to DMA map RX\n");
return 0;
}
drv_data->rx_nents = ret;
return 1;
}
irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data) irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data)
{ {
u32 status; u32 status;
...@@ -245,8 +133,8 @@ irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data) ...@@ -245,8 +133,8 @@ irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data)
if (status & SSSR_ROR) { if (status & SSSR_ROR) {
dev_err(&drv_data->pdev->dev, "FIFO overrun\n"); dev_err(&drv_data->pdev->dev, "FIFO overrun\n");
dmaengine_terminate_async(drv_data->rx_chan); dmaengine_terminate_async(drv_data->master->dma_rx);
dmaengine_terminate_async(drv_data->tx_chan); dmaengine_terminate_async(drv_data->master->dma_tx);
pxa2xx_spi_dma_transfer_complete(drv_data, true); pxa2xx_spi_dma_transfer_complete(drv_data, true);
return IRQ_HANDLED; return IRQ_HANDLED;
...@@ -285,16 +173,15 @@ int pxa2xx_spi_dma_prepare(struct driver_data *drv_data, u32 dma_burst) ...@@ -285,16 +173,15 @@ int pxa2xx_spi_dma_prepare(struct driver_data *drv_data, u32 dma_burst)
return 0; return 0;
err_rx: err_rx:
dmaengine_terminate_async(drv_data->tx_chan); dmaengine_terminate_async(drv_data->master->dma_tx);
err_tx: err_tx:
pxa2xx_spi_unmap_dma_buffers(drv_data);
return err; return err;
} }
void pxa2xx_spi_dma_start(struct driver_data *drv_data) void pxa2xx_spi_dma_start(struct driver_data *drv_data)
{ {
dma_async_issue_pending(drv_data->rx_chan); dma_async_issue_pending(drv_data->master->dma_rx);
dma_async_issue_pending(drv_data->tx_chan); dma_async_issue_pending(drv_data->master->dma_tx);
atomic_set(&drv_data->dma_running, 1); atomic_set(&drv_data->dma_running, 1);
} }
...@@ -303,21 +190,22 @@ int pxa2xx_spi_dma_setup(struct driver_data *drv_data) ...@@ -303,21 +190,22 @@ int pxa2xx_spi_dma_setup(struct driver_data *drv_data)
{ {
struct pxa2xx_spi_master *pdata = drv_data->master_info; struct pxa2xx_spi_master *pdata = drv_data->master_info;
struct device *dev = &drv_data->pdev->dev; struct device *dev = &drv_data->pdev->dev;
struct spi_master *master = drv_data->master;
dma_cap_mask_t mask; dma_cap_mask_t mask;
dma_cap_zero(mask); dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask); dma_cap_set(DMA_SLAVE, mask);
drv_data->tx_chan = dma_request_slave_channel_compat(mask, master->dma_tx = dma_request_slave_channel_compat(mask,
pdata->dma_filter, pdata->tx_param, dev, "tx"); pdata->dma_filter, pdata->tx_param, dev, "tx");
if (!drv_data->tx_chan) if (!master->dma_tx)
return -ENODEV; return -ENODEV;
drv_data->rx_chan = dma_request_slave_channel_compat(mask, master->dma_rx = dma_request_slave_channel_compat(mask,
pdata->dma_filter, pdata->rx_param, dev, "rx"); pdata->dma_filter, pdata->rx_param, dev, "rx");
if (!drv_data->rx_chan) { if (!master->dma_rx) {
dma_release_channel(drv_data->tx_chan); dma_release_channel(master->dma_tx);
drv_data->tx_chan = NULL; master->dma_tx = NULL;
return -ENODEV; return -ENODEV;
} }
...@@ -326,17 +214,17 @@ int pxa2xx_spi_dma_setup(struct driver_data *drv_data) ...@@ -326,17 +214,17 @@ int pxa2xx_spi_dma_setup(struct driver_data *drv_data)
void pxa2xx_spi_dma_release(struct driver_data *drv_data) void pxa2xx_spi_dma_release(struct driver_data *drv_data)
{ {
if (drv_data->rx_chan) { struct spi_master *master = drv_data->master;
dmaengine_terminate_sync(drv_data->rx_chan);
dma_release_channel(drv_data->rx_chan); if (master->dma_rx) {
sg_free_table(&drv_data->rx_sgt); dmaengine_terminate_sync(master->dma_rx);
drv_data->rx_chan = NULL; dma_release_channel(master->dma_rx);
master->dma_rx = NULL;
} }
if (drv_data->tx_chan) { if (master->dma_tx) {
dmaengine_terminate_sync(drv_data->tx_chan); dmaengine_terminate_sync(master->dma_tx);
dma_release_channel(drv_data->tx_chan); dma_release_channel(master->dma_tx);
sg_free_table(&drv_data->tx_sgt); master->dma_tx = NULL;
drv_data->tx_chan = NULL;
} }
} }
......
/* /*
* CE4100's SPI device is more or less the same one as found on PXA * CE4100's SPI device is more or less the same one as found on PXA
* *
* Copyright (C) 2016, Intel Corporation
*/ */
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/pci.h> #include <linux/pci.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/module.h>
#include <linux/spi/pxa2xx_spi.h> #include <linux/spi/pxa2xx_spi.h>
#include <linux/clk-provider.h>
#include <linux/dmaengine.h> #include <linux/dmaengine.h>
#include <linux/platform_data/dma-dw.h> #include <linux/platform_data/dma-dw.h>
enum { enum {
PORT_CE4100, PORT_QUARK_X1000,
PORT_BYT, PORT_BYT,
PORT_MRFLD,
PORT_BSW0, PORT_BSW0,
PORT_BSW1, PORT_BSW1,
PORT_BSW2, PORT_BSW2,
PORT_QUARK_X1000, PORT_CE4100,
PORT_LPT, PORT_LPT,
}; };
...@@ -29,8 +31,11 @@ struct pxa_spi_info { ...@@ -29,8 +31,11 @@ struct pxa_spi_info {
unsigned long max_clk_rate; unsigned long max_clk_rate;
/* DMA channel request parameters */ /* DMA channel request parameters */
bool (*dma_filter)(struct dma_chan *chan, void *param);
void *tx_param; void *tx_param;
void *rx_param; void *rx_param;
int (*setup)(struct pci_dev *pdev, struct pxa_spi_info *c);
}; };
static struct dw_dma_slave byt_tx_param = { .dst_id = 0 }; static struct dw_dma_slave byt_tx_param = { .dst_id = 0 };
...@@ -57,6 +62,56 @@ static bool lpss_dma_filter(struct dma_chan *chan, void *param) ...@@ -57,6 +62,56 @@ static bool lpss_dma_filter(struct dma_chan *chan, void *param)
return true; return true;
} }
static int lpss_spi_setup(struct pci_dev *dev, struct pxa_spi_info *c)
{
struct pci_dev *dma_dev;
c->num_chipselect = 1;
c->max_clk_rate = 50000000;
dma_dev = pci_get_slot(dev->bus, PCI_DEVFN(PCI_SLOT(dev->devfn), 0));
if (c->tx_param) {
struct dw_dma_slave *slave = c->tx_param;
slave->dma_dev = &dma_dev->dev;
slave->m_master = 0;
slave->p_master = 1;
}
if (c->rx_param) {
struct dw_dma_slave *slave = c->rx_param;
slave->dma_dev = &dma_dev->dev;
slave->m_master = 0;
slave->p_master = 1;
}
c->dma_filter = lpss_dma_filter;
return 0;
}
static int mrfld_spi_setup(struct pci_dev *dev, struct pxa_spi_info *c)
{
switch (PCI_FUNC(dev->devfn)) {
case 0:
c->port_id = 3;
c->num_chipselect = 1;
break;
case 1:
c->port_id = 5;
c->num_chipselect = 4;
break;
case 2:
c->port_id = 6;
c->num_chipselect = 1;
break;
default:
return -ENODEV;
}
return 0;
}
static struct pxa_spi_info spi_info_configs[] = { static struct pxa_spi_info spi_info_configs[] = {
[PORT_CE4100] = { [PORT_CE4100] = {
.type = PXA25x_SSP, .type = PXA25x_SSP,
...@@ -67,35 +122,36 @@ static struct pxa_spi_info spi_info_configs[] = { ...@@ -67,35 +122,36 @@ static struct pxa_spi_info spi_info_configs[] = {
[PORT_BYT] = { [PORT_BYT] = {
.type = LPSS_BYT_SSP, .type = LPSS_BYT_SSP,
.port_id = 0, .port_id = 0,
.num_chipselect = 1, .setup = lpss_spi_setup,
.max_clk_rate = 50000000,
.tx_param = &byt_tx_param, .tx_param = &byt_tx_param,
.rx_param = &byt_rx_param, .rx_param = &byt_rx_param,
}, },
[PORT_BSW0] = { [PORT_BSW0] = {
.type = LPSS_BYT_SSP, .type = LPSS_BSW_SSP,
.port_id = 0, .port_id = 0,
.num_chipselect = 1, .setup = lpss_spi_setup,
.max_clk_rate = 50000000,
.tx_param = &bsw0_tx_param, .tx_param = &bsw0_tx_param,
.rx_param = &bsw0_rx_param, .rx_param = &bsw0_rx_param,
}, },
[PORT_BSW1] = { [PORT_BSW1] = {
.type = LPSS_BYT_SSP, .type = LPSS_BSW_SSP,
.port_id = 1, .port_id = 1,
.num_chipselect = 1, .setup = lpss_spi_setup,
.max_clk_rate = 50000000,
.tx_param = &bsw1_tx_param, .tx_param = &bsw1_tx_param,
.rx_param = &bsw1_rx_param, .rx_param = &bsw1_rx_param,
}, },
[PORT_BSW2] = { [PORT_BSW2] = {
.type = LPSS_BYT_SSP, .type = LPSS_BSW_SSP,
.port_id = 2, .port_id = 2,
.num_chipselect = 1, .setup = lpss_spi_setup,
.max_clk_rate = 50000000,
.tx_param = &bsw2_tx_param, .tx_param = &bsw2_tx_param,
.rx_param = &bsw2_rx_param, .rx_param = &bsw2_rx_param,
}, },
[PORT_MRFLD] = {
.type = PXA27x_SSP,
.max_clk_rate = 25000000,
.setup = mrfld_spi_setup,
},
[PORT_QUARK_X1000] = { [PORT_QUARK_X1000] = {
.type = QUARK_X1000_SSP, .type = QUARK_X1000_SSP,
.port_id = -1, .port_id = -1,
...@@ -105,8 +161,7 @@ static struct pxa_spi_info spi_info_configs[] = { ...@@ -105,8 +161,7 @@ static struct pxa_spi_info spi_info_configs[] = {
[PORT_LPT] = { [PORT_LPT] = {
.type = LPSS_LPT_SSP, .type = LPSS_LPT_SSP,
.port_id = 0, .port_id = 0,
.num_chipselect = 1, .setup = lpss_spi_setup,
.max_clk_rate = 50000000,
.tx_param = &lpt_tx_param, .tx_param = &lpt_tx_param,
.rx_param = &lpt_rx_param, .rx_param = &lpt_rx_param,
}, },
...@@ -122,7 +177,6 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev, ...@@ -122,7 +177,6 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
struct ssp_device *ssp; struct ssp_device *ssp;
struct pxa_spi_info *c; struct pxa_spi_info *c;
char buf[40]; char buf[40];
struct pci_dev *dma_dev;
ret = pcim_enable_device(dev); ret = pcim_enable_device(dev);
if (ret) if (ret)
...@@ -133,30 +187,15 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev, ...@@ -133,30 +187,15 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
return ret; return ret;
c = &spi_info_configs[ent->driver_data]; c = &spi_info_configs[ent->driver_data];
if (c->setup) {
memset(&spi_pdata, 0, sizeof(spi_pdata)); ret = c->setup(dev, c);
spi_pdata.num_chipselect = (c->num_chipselect > 0) ? if (ret)
c->num_chipselect : dev->devfn; return ret;
dma_dev = pci_get_slot(dev->bus, PCI_DEVFN(PCI_SLOT(dev->devfn), 0));
if (c->tx_param) {
struct dw_dma_slave *slave = c->tx_param;
slave->dma_dev = &dma_dev->dev;
slave->m_master = 0;
slave->p_master = 1;
}
if (c->rx_param) {
struct dw_dma_slave *slave = c->rx_param;
slave->dma_dev = &dma_dev->dev;
slave->m_master = 0;
slave->p_master = 1;
} }
spi_pdata.dma_filter = lpss_dma_filter; memset(&spi_pdata, 0, sizeof(spi_pdata));
spi_pdata.num_chipselect = (c->num_chipselect > 0) ? c->num_chipselect : dev->devfn;
spi_pdata.dma_filter = c->dma_filter;
spi_pdata.tx_param = c->tx_param; spi_pdata.tx_param = c->tx_param;
spi_pdata.rx_param = c->rx_param; spi_pdata.rx_param = c->rx_param;
spi_pdata.enable_dma = c->rx_param && c->tx_param; spi_pdata.enable_dma = c->rx_param && c->tx_param;
...@@ -164,10 +203,6 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev, ...@@ -164,10 +203,6 @@ static int pxa2xx_spi_pci_probe(struct pci_dev *dev,
ssp = &spi_pdata.ssp; ssp = &spi_pdata.ssp;
ssp->phys_base = pci_resource_start(dev, 0); ssp->phys_base = pci_resource_start(dev, 0);
ssp->mmio_base = pcim_iomap_table(dev)[0]; ssp->mmio_base = pcim_iomap_table(dev)[0];
if (!ssp->mmio_base) {
dev_err(&dev->dev, "failed to ioremap() registers\n");
return -EIO;
}
ssp->irq = dev->irq; ssp->irq = dev->irq;
ssp->port_id = (c->port_id >= 0) ? c->port_id : dev->devfn; ssp->port_id = (c->port_id >= 0) ? c->port_id : dev->devfn;
ssp->type = c->type; ssp->type = c->type;
...@@ -208,12 +243,13 @@ static void pxa2xx_spi_pci_remove(struct pci_dev *dev) ...@@ -208,12 +243,13 @@ static void pxa2xx_spi_pci_remove(struct pci_dev *dev)
} }
static const struct pci_device_id pxa2xx_spi_pci_devices[] = { static const struct pci_device_id pxa2xx_spi_pci_devices[] = {
{ PCI_VDEVICE(INTEL, 0x2e6a), PORT_CE4100 },
{ PCI_VDEVICE(INTEL, 0x0935), PORT_QUARK_X1000 }, { PCI_VDEVICE(INTEL, 0x0935), PORT_QUARK_X1000 },
{ PCI_VDEVICE(INTEL, 0x0f0e), PORT_BYT }, { PCI_VDEVICE(INTEL, 0x0f0e), PORT_BYT },
{ PCI_VDEVICE(INTEL, 0x1194), PORT_MRFLD },
{ PCI_VDEVICE(INTEL, 0x228e), PORT_BSW0 }, { PCI_VDEVICE(INTEL, 0x228e), PORT_BSW0 },
{ PCI_VDEVICE(INTEL, 0x2290), PORT_BSW1 }, { PCI_VDEVICE(INTEL, 0x2290), PORT_BSW1 },
{ PCI_VDEVICE(INTEL, 0x22ac), PORT_BSW2 }, { PCI_VDEVICE(INTEL, 0x22ac), PORT_BSW2 },
{ PCI_VDEVICE(INTEL, 0x2e6a), PORT_CE4100 },
{ PCI_VDEVICE(INTEL, 0x9ce6), PORT_LPT }, { PCI_VDEVICE(INTEL, 0x9ce6), PORT_LPT },
{ }, { },
}; };
......
...@@ -919,9 +919,21 @@ static unsigned int pxa2xx_ssp_get_clk_div(struct driver_data *drv_data, ...@@ -919,9 +919,21 @@ static unsigned int pxa2xx_ssp_get_clk_div(struct driver_data *drv_data,
return clk_div << 8; return clk_div << 8;
} }
static bool pxa2xx_spi_can_dma(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *xfer)
{
struct chip_data *chip = spi_get_ctldata(spi);
return chip->enable_dma &&
xfer->len <= MAX_DMA_LEN &&
xfer->len >= chip->dma_burst_size;
}
static void pump_transfers(unsigned long data) static void pump_transfers(unsigned long data)
{ {
struct driver_data *drv_data = (struct driver_data *)data; struct driver_data *drv_data = (struct driver_data *)data;
struct spi_master *master = drv_data->master;
struct spi_message *message = NULL; struct spi_message *message = NULL;
struct spi_transfer *transfer = NULL; struct spi_transfer *transfer = NULL;
struct spi_transfer *previous = NULL; struct spi_transfer *previous = NULL;
...@@ -935,6 +947,7 @@ static void pump_transfers(unsigned long data) ...@@ -935,6 +947,7 @@ static void pump_transfers(unsigned long data)
u32 dma_burst = drv_data->cur_chip->dma_burst_size; u32 dma_burst = drv_data->cur_chip->dma_burst_size;
u32 change_mask = pxa2xx_spi_get_ssrc1_change_mask(drv_data); u32 change_mask = pxa2xx_spi_get_ssrc1_change_mask(drv_data);
int err; int err;
int dma_mapped;
/* Get current state information */ /* Get current state information */
message = drv_data->cur_msg; message = drv_data->cur_msg;
...@@ -969,7 +982,7 @@ static void pump_transfers(unsigned long data) ...@@ -969,7 +982,7 @@ static void pump_transfers(unsigned long data)
} }
/* Check if we can DMA this transfer */ /* Check if we can DMA this transfer */
if (!pxa2xx_spi_dma_is_possible(transfer->len) && chip->enable_dma) { if (transfer->len > MAX_DMA_LEN && chip->enable_dma) {
/* reject already-mapped transfers; PIO won't always work */ /* reject already-mapped transfers; PIO won't always work */
if (message->is_dma_mapped if (message->is_dma_mapped
...@@ -1046,10 +1059,10 @@ static void pump_transfers(unsigned long data) ...@@ -1046,10 +1059,10 @@ static void pump_transfers(unsigned long data)
message->state = RUNNING_STATE; message->state = RUNNING_STATE;
drv_data->dma_mapped = 0; dma_mapped = master->can_dma &&
if (pxa2xx_spi_dma_is_possible(drv_data->len)) master->can_dma(master, message->spi, transfer) &&
drv_data->dma_mapped = pxa2xx_spi_map_dma_buffers(drv_data); master->cur_msg_mapped;
if (drv_data->dma_mapped) { if (dma_mapped) {
/* Ensure we have the correct interrupt handler */ /* Ensure we have the correct interrupt handler */
drv_data->transfer_handler = pxa2xx_spi_dma_transfer; drv_data->transfer_handler = pxa2xx_spi_dma_transfer;
...@@ -1079,14 +1092,14 @@ static void pump_transfers(unsigned long data) ...@@ -1079,14 +1092,14 @@ static void pump_transfers(unsigned long data)
cr0 = pxa2xx_configure_sscr0(drv_data, clk_div, bits); cr0 = pxa2xx_configure_sscr0(drv_data, clk_div, bits);
if (!pxa25x_ssp_comp(drv_data)) if (!pxa25x_ssp_comp(drv_data))
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n", dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
drv_data->master->max_speed_hz master->max_speed_hz
/ (1 + ((cr0 & SSCR0_SCR(0xfff)) >> 8)), / (1 + ((cr0 & SSCR0_SCR(0xfff)) >> 8)),
drv_data->dma_mapped ? "DMA" : "PIO"); dma_mapped ? "DMA" : "PIO");
else else
dev_dbg(&message->spi->dev, "%u Hz actual, %s\n", dev_dbg(&message->spi->dev, "%u Hz actual, %s\n",
drv_data->master->max_speed_hz / 2 master->max_speed_hz / 2
/ (1 + ((cr0 & SSCR0_SCR(0x0ff)) >> 8)), / (1 + ((cr0 & SSCR0_SCR(0x0ff)) >> 8)),
drv_data->dma_mapped ? "DMA" : "PIO"); dma_mapped ? "DMA" : "PIO");
if (is_lpss_ssp(drv_data)) { if (is_lpss_ssp(drv_data)) {
if ((pxa2xx_spi_read(drv_data, SSIRF) & 0xff) if ((pxa2xx_spi_read(drv_data, SSIRF) & 0xff)
...@@ -1247,7 +1260,7 @@ static int setup(struct spi_device *spi) ...@@ -1247,7 +1260,7 @@ static int setup(struct spi_device *spi)
chip->frm = spi->chip_select; chip->frm = spi->chip_select;
} else } else
chip->gpio_cs = -1; chip->gpio_cs = -1;
chip->enable_dma = 0; chip->enable_dma = drv_data->master_info->enable_dma;
chip->timeout = TIMOUT_DFLT; chip->timeout = TIMOUT_DFLT;
} }
...@@ -1266,17 +1279,9 @@ static int setup(struct spi_device *spi) ...@@ -1266,17 +1279,9 @@ static int setup(struct spi_device *spi)
tx_hi_thres = chip_info->tx_hi_threshold; tx_hi_thres = chip_info->tx_hi_threshold;
if (chip_info->rx_threshold) if (chip_info->rx_threshold)
rx_thres = chip_info->rx_threshold; rx_thres = chip_info->rx_threshold;
chip->enable_dma = drv_data->master_info->enable_dma;
chip->dma_threshold = 0; chip->dma_threshold = 0;
if (chip_info->enable_loopback) if (chip_info->enable_loopback)
chip->cr1 = SSCR1_LBM; chip->cr1 = SSCR1_LBM;
} else if (ACPI_HANDLE(&spi->dev)) {
/*
* Slave devices enumerated from ACPI namespace don't
* usually have chip_info but we still might want to use
* DMA with them.
*/
chip->enable_dma = drv_data->master_info->enable_dma;
} }
chip->lpss_rx_threshold = SSIRF_RxThresh(rx_thres); chip->lpss_rx_threshold = SSIRF_RxThresh(rx_thres);
...@@ -1396,6 +1401,9 @@ static const struct pci_device_id pxa2xx_spi_pci_compound_match[] = { ...@@ -1396,6 +1401,9 @@ static const struct pci_device_id pxa2xx_spi_pci_compound_match[] = {
/* SPT-H */ /* SPT-H */
{ PCI_VDEVICE(INTEL, 0xa129), LPSS_SPT_SSP }, { PCI_VDEVICE(INTEL, 0xa129), LPSS_SPT_SSP },
{ PCI_VDEVICE(INTEL, 0xa12a), LPSS_SPT_SSP }, { PCI_VDEVICE(INTEL, 0xa12a), LPSS_SPT_SSP },
/* KBL-H */
{ PCI_VDEVICE(INTEL, 0xa2a9), LPSS_SPT_SSP },
{ PCI_VDEVICE(INTEL, 0xa2aa), LPSS_SPT_SSP },
/* BXT A-Step */ /* BXT A-Step */
{ PCI_VDEVICE(INTEL, 0x0ac2), LPSS_BXT_SSP }, { PCI_VDEVICE(INTEL, 0x0ac2), LPSS_BXT_SSP },
{ PCI_VDEVICE(INTEL, 0x0ac4), LPSS_BXT_SSP }, { PCI_VDEVICE(INTEL, 0x0ac4), LPSS_BXT_SSP },
...@@ -1608,6 +1616,8 @@ static int pxa2xx_spi_probe(struct platform_device *pdev) ...@@ -1608,6 +1616,8 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
if (status) { if (status) {
dev_dbg(dev, "no DMA channels available, using PIO\n"); dev_dbg(dev, "no DMA channels available, using PIO\n");
platform_info->enable_dma = false; platform_info->enable_dma = false;
} else {
master->can_dma = pxa2xx_spi_can_dma;
} }
} }
......
...@@ -50,12 +50,6 @@ struct driver_data { ...@@ -50,12 +50,6 @@ struct driver_data {
struct tasklet_struct pump_transfers; struct tasklet_struct pump_transfers;
/* DMA engine support */ /* DMA engine support */
struct dma_chan *rx_chan;
struct dma_chan *tx_chan;
struct sg_table rx_sgt;
struct sg_table tx_sgt;
int rx_nents;
int tx_nents;
atomic_t dma_running; atomic_t dma_running;
/* Current message transfer state info */ /* Current message transfer state info */
...@@ -67,7 +61,6 @@ struct driver_data { ...@@ -67,7 +61,6 @@ struct driver_data {
void *tx_end; void *tx_end;
void *rx; void *rx;
void *rx_end; void *rx_end;
int dma_mapped;
u8 n_bytes; u8 n_bytes;
int (*write)(struct driver_data *drv_data); int (*write)(struct driver_data *drv_data);
int (*read)(struct driver_data *drv_data); int (*read)(struct driver_data *drv_data);
...@@ -145,8 +138,6 @@ extern void *pxa2xx_spi_next_transfer(struct driver_data *drv_data); ...@@ -145,8 +138,6 @@ extern void *pxa2xx_spi_next_transfer(struct driver_data *drv_data);
#define MAX_DMA_LEN SZ_64K #define MAX_DMA_LEN SZ_64K
#define DEFAULT_DMA_CR1 (SSCR1_TSRE | SSCR1_RSRE | SSCR1_TRAIL) #define DEFAULT_DMA_CR1 (SSCR1_TSRE | SSCR1_RSRE | SSCR1_TRAIL)
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); extern irqreturn_t pxa2xx_spi_dma_transfer(struct driver_data *drv_data);
extern int pxa2xx_spi_dma_prepare(struct driver_data *drv_data, u32 dma_burst); extern int pxa2xx_spi_dma_prepare(struct driver_data *drv_data, u32 dma_burst);
extern void pxa2xx_spi_dma_start(struct driver_data *drv_data); extern void pxa2xx_spi_dma_start(struct driver_data *drv_data);
......
...@@ -911,9 +911,12 @@ static const struct dev_pm_ops rockchip_spi_pm = { ...@@ -911,9 +911,12 @@ static const struct dev_pm_ops rockchip_spi_pm = {
}; };
static const struct of_device_id rockchip_spi_dt_match[] = { static const struct of_device_id rockchip_spi_dt_match[] = {
{ .compatible = "rockchip,rk3036-spi", },
{ .compatible = "rockchip,rk3066-spi", }, { .compatible = "rockchip,rk3066-spi", },
{ .compatible = "rockchip,rk3188-spi", }, { .compatible = "rockchip,rk3188-spi", },
{ .compatible = "rockchip,rk3228-spi", },
{ .compatible = "rockchip,rk3288-spi", }, { .compatible = "rockchip,rk3288-spi", },
{ .compatible = "rockchip,rk3368-spi", },
{ .compatible = "rockchip,rk3399-spi", }, { .compatible = "rockchip,rk3399-spi", },
{ }, { },
}; };
......
...@@ -156,12 +156,14 @@ struct s3c64xx_spi_port_config { ...@@ -156,12 +156,14 @@ struct s3c64xx_spi_port_config {
int quirks; int quirks;
bool high_speed; bool high_speed;
bool clk_from_cmu; bool clk_from_cmu;
bool clk_ioclk;
}; };
/** /**
* struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver. * struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver.
* @clk: Pointer to the spi clock. * @clk: Pointer to the spi clock.
* @src_clk: Pointer to the clock used to generate SPI signals. * @src_clk: Pointer to the clock used to generate SPI signals.
* @ioclk: Pointer to the i/o clock between master and slave
* @master: Pointer to the SPI Protocol master. * @master: Pointer to the SPI Protocol master.
* @cntrlr_info: Platform specific data for the controller this driver manages. * @cntrlr_info: Platform specific data for the controller this driver manages.
* @tgl_spi: Pointer to the last CS left untoggled by the cs_change hint. * @tgl_spi: Pointer to the last CS left untoggled by the cs_change hint.
...@@ -181,6 +183,7 @@ struct s3c64xx_spi_driver_data { ...@@ -181,6 +183,7 @@ struct s3c64xx_spi_driver_data {
void __iomem *regs; void __iomem *regs;
struct clk *clk; struct clk *clk;
struct clk *src_clk; struct clk *src_clk;
struct clk *ioclk;
struct platform_device *pdev; struct platform_device *pdev;
struct spi_master *master; struct spi_master *master;
struct s3c64xx_spi_info *cntrlr_info; struct s3c64xx_spi_info *cntrlr_info;
...@@ -310,15 +313,41 @@ static void prepare_dma(struct s3c64xx_spi_dma_data *dma, ...@@ -310,15 +313,41 @@ static void prepare_dma(struct s3c64xx_spi_dma_data *dma,
dma_async_issue_pending(dma->ch); dma_async_issue_pending(dma->ch);
} }
static void s3c64xx_spi_set_cs(struct spi_device *spi, bool enable)
{
struct s3c64xx_spi_driver_data *sdd =
spi_master_get_devdata(spi->master);
if (sdd->cntrlr_info->no_cs)
return;
if (enable) {
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) {
writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
} else {
u32 ssel = readl(sdd->regs + S3C64XX_SPI_SLAVE_SEL);
ssel |= (S3C64XX_SPI_SLAVE_AUTO |
S3C64XX_SPI_SLAVE_NSC_CNT_2);
writel(ssel, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
}
} else {
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
writel(S3C64XX_SPI_SLAVE_SIG_INACT,
sdd->regs + S3C64XX_SPI_SLAVE_SEL);
}
}
static int s3c64xx_spi_prepare_transfer(struct spi_master *spi) static int s3c64xx_spi_prepare_transfer(struct spi_master *spi)
{ {
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(spi); struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(spi);
dma_filter_fn filter = sdd->cntrlr_info->filter; dma_filter_fn filter = sdd->cntrlr_info->filter;
struct device *dev = &sdd->pdev->dev; struct device *dev = &sdd->pdev->dev;
dma_cap_mask_t mask; dma_cap_mask_t mask;
int ret;
if (!is_polling(sdd)) { if (is_polling(sdd))
return 0;
dma_cap_zero(mask); dma_cap_zero(mask);
dma_cap_set(DMA_SLAVE, mask); dma_cap_set(DMA_SLAVE, mask);
...@@ -327,8 +356,7 @@ static int s3c64xx_spi_prepare_transfer(struct spi_master *spi) ...@@ -327,8 +356,7 @@ static int s3c64xx_spi_prepare_transfer(struct spi_master *spi)
sdd->cntrlr_info->dma_rx, dev, "rx"); sdd->cntrlr_info->dma_rx, dev, "rx");
if (!sdd->rx_dma.ch) { if (!sdd->rx_dma.ch) {
dev_err(dev, "Failed to get RX DMA channel\n"); dev_err(dev, "Failed to get RX DMA channel\n");
ret = -EBUSY; return -EBUSY;
goto out;
} }
spi->dma_rx = sdd->rx_dma.ch; spi->dma_rx = sdd->rx_dma.ch;
...@@ -336,18 +364,12 @@ static int s3c64xx_spi_prepare_transfer(struct spi_master *spi) ...@@ -336,18 +364,12 @@ static int s3c64xx_spi_prepare_transfer(struct spi_master *spi)
sdd->cntrlr_info->dma_tx, dev, "tx"); sdd->cntrlr_info->dma_tx, dev, "tx");
if (!sdd->tx_dma.ch) { if (!sdd->tx_dma.ch) {
dev_err(dev, "Failed to get TX DMA channel\n"); dev_err(dev, "Failed to get TX DMA channel\n");
ret = -EBUSY; dma_release_channel(sdd->rx_dma.ch);
goto out_rx; return -EBUSY;
} }
spi->dma_tx = sdd->tx_dma.ch; spi->dma_tx = sdd->tx_dma.ch;
}
return 0; return 0;
out_rx:
dma_release_channel(sdd->rx_dma.ch);
out:
return ret;
} }
static int s3c64xx_spi_unprepare_transfer(struct spi_master *spi) static int s3c64xx_spi_unprepare_transfer(struct spi_master *spi)
...@@ -577,9 +599,7 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd) ...@@ -577,9 +599,7 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd)
u32 val; u32 val;
/* Disable Clock */ /* Disable Clock */
if (sdd->port_conf->clk_from_cmu) { if (!sdd->port_conf->clk_from_cmu) {
clk_disable_unprepare(sdd->src_clk);
} else {
val = readl(regs + S3C64XX_SPI_CLK_CFG); val = readl(regs + S3C64XX_SPI_CLK_CFG);
val &= ~S3C64XX_SPI_ENCLK_ENABLE; val &= ~S3C64XX_SPI_ENCLK_ENABLE;
writel(val, regs + S3C64XX_SPI_CLK_CFG); writel(val, regs + S3C64XX_SPI_CLK_CFG);
...@@ -622,11 +642,8 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd) ...@@ -622,11 +642,8 @@ static void s3c64xx_spi_config(struct s3c64xx_spi_driver_data *sdd)
writel(val, regs + S3C64XX_SPI_MODE_CFG); writel(val, regs + S3C64XX_SPI_MODE_CFG);
if (sdd->port_conf->clk_from_cmu) { if (sdd->port_conf->clk_from_cmu) {
/* Configure Clock */ /* The src_clk clock is divided internally by 2 */
/* There is half-multiplier before the SPI */
clk_set_rate(sdd->src_clk, sdd->cur_speed * 2); clk_set_rate(sdd->src_clk, sdd->cur_speed * 2);
/* Enable Clock */
clk_prepare_enable(sdd->src_clk);
} else { } else {
/* Configure Clock */ /* Configure Clock */
val = readl(regs + S3C64XX_SPI_CLK_CFG); val = readl(regs + S3C64XX_SPI_CLK_CFG);
...@@ -651,16 +668,6 @@ static int s3c64xx_spi_prepare_message(struct spi_master *master, ...@@ -651,16 +668,6 @@ static int s3c64xx_spi_prepare_message(struct spi_master *master,
struct spi_device *spi = msg->spi; struct spi_device *spi = msg->spi;
struct s3c64xx_spi_csinfo *cs = spi->controller_data; struct s3c64xx_spi_csinfo *cs = spi->controller_data;
/* If Master's(controller) state differs from that needed by Slave */
if (sdd->cur_speed != spi->max_speed_hz
|| sdd->cur_mode != spi->mode
|| sdd->cur_bpw != spi->bits_per_word) {
sdd->cur_bpw = spi->bits_per_word;
sdd->cur_speed = spi->max_speed_hz;
sdd->cur_mode = spi->mode;
s3c64xx_spi_config(sdd);
}
/* Configure feedback delay */ /* Configure feedback delay */
writel(cs->fb_delay & 0x3, sdd->regs + S3C64XX_SPI_FB_CLK); writel(cs->fb_delay & 0x3, sdd->regs + S3C64XX_SPI_FB_CLK);
...@@ -687,6 +694,7 @@ static int s3c64xx_spi_transfer_one(struct spi_master *master, ...@@ -687,6 +694,7 @@ static int s3c64xx_spi_transfer_one(struct spi_master *master,
if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) { if (bpw != sdd->cur_bpw || speed != sdd->cur_speed) {
sdd->cur_bpw = bpw; sdd->cur_bpw = bpw;
sdd->cur_speed = speed; sdd->cur_speed = speed;
sdd->cur_mode = spi->mode;
s3c64xx_spi_config(sdd); s3c64xx_spi_config(sdd);
} }
...@@ -706,12 +714,7 @@ static int s3c64xx_spi_transfer_one(struct spi_master *master, ...@@ -706,12 +714,7 @@ static int s3c64xx_spi_transfer_one(struct spi_master *master,
enable_datapath(sdd, spi, xfer, use_dma); enable_datapath(sdd, spi, xfer, use_dma);
/* Start the signals */ /* Start the signals */
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) s3c64xx_spi_set_cs(spi, true);
writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
else
writel(readl(sdd->regs + S3C64XX_SPI_SLAVE_SEL)
| S3C64XX_SPI_SLAVE_AUTO | S3C64XX_SPI_SLAVE_NSC_CNT_2,
sdd->regs + S3C64XX_SPI_SLAVE_SEL);
spin_unlock_irqrestore(&sdd->lock, flags); spin_unlock_irqrestore(&sdd->lock, flags);
...@@ -861,16 +864,15 @@ static int s3c64xx_spi_setup(struct spi_device *spi) ...@@ -861,16 +864,15 @@ static int s3c64xx_spi_setup(struct spi_device *spi)
pm_runtime_mark_last_busy(&sdd->pdev->dev); pm_runtime_mark_last_busy(&sdd->pdev->dev);
pm_runtime_put_autosuspend(&sdd->pdev->dev); pm_runtime_put_autosuspend(&sdd->pdev->dev);
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) s3c64xx_spi_set_cs(spi, false);
writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
return 0; return 0;
setup_exit: setup_exit:
pm_runtime_mark_last_busy(&sdd->pdev->dev); pm_runtime_mark_last_busy(&sdd->pdev->dev);
pm_runtime_put_autosuspend(&sdd->pdev->dev); pm_runtime_put_autosuspend(&sdd->pdev->dev);
/* setup() returns with device de-selected */ /* setup() returns with device de-selected */
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) s3c64xx_spi_set_cs(spi, false);
writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
if (gpio_is_valid(spi->cs_gpio)) if (gpio_is_valid(spi->cs_gpio))
gpio_free(spi->cs_gpio); gpio_free(spi->cs_gpio);
...@@ -944,7 +946,9 @@ static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel) ...@@ -944,7 +946,9 @@ static void s3c64xx_spi_hwinit(struct s3c64xx_spi_driver_data *sdd, int channel)
sdd->cur_speed = 0; sdd->cur_speed = 0;
if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO)) if (sci->no_cs)
writel(0, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
else if (!(sdd->port_conf->quirks & S3C64XX_SPI_QUIRK_CS_AUTO))
writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL); writel(S3C64XX_SPI_SLAVE_SIG_INACT, sdd->regs + S3C64XX_SPI_SLAVE_SEL);
/* Disable Interrupts - we use Polling if not DMA mode */ /* Disable Interrupts - we use Polling if not DMA mode */
...@@ -999,6 +1003,8 @@ static struct s3c64xx_spi_info *s3c64xx_spi_parse_dt(struct device *dev) ...@@ -999,6 +1003,8 @@ static struct s3c64xx_spi_info *s3c64xx_spi_parse_dt(struct device *dev)
sci->num_cs = temp; sci->num_cs = temp;
} }
sci->no_cs = of_property_read_bool(dev->of_node, "broken-cs");
return sci; return sci;
} }
#else #else
...@@ -1076,7 +1082,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ...@@ -1076,7 +1082,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
if (ret < 0) { if (ret < 0) {
dev_err(&pdev->dev, "failed to get alias id, errno %d\n", dev_err(&pdev->dev, "failed to get alias id, errno %d\n",
ret); ret);
goto err0; goto err_deref_master;
} }
sdd->port_id = ret; sdd->port_id = ret;
} else { } else {
...@@ -1114,13 +1120,13 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ...@@ -1114,13 +1120,13 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
sdd->regs = devm_ioremap_resource(&pdev->dev, mem_res); sdd->regs = devm_ioremap_resource(&pdev->dev, mem_res);
if (IS_ERR(sdd->regs)) { if (IS_ERR(sdd->regs)) {
ret = PTR_ERR(sdd->regs); ret = PTR_ERR(sdd->regs);
goto err0; goto err_deref_master;
} }
if (sci->cfg_gpio && sci->cfg_gpio()) { if (sci->cfg_gpio && sci->cfg_gpio()) {
dev_err(&pdev->dev, "Unable to config gpio\n"); dev_err(&pdev->dev, "Unable to config gpio\n");
ret = -EBUSY; ret = -EBUSY;
goto err0; goto err_deref_master;
} }
/* Setup clocks */ /* Setup clocks */
...@@ -1128,13 +1134,13 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ...@@ -1128,13 +1134,13 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
if (IS_ERR(sdd->clk)) { if (IS_ERR(sdd->clk)) {
dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n"); dev_err(&pdev->dev, "Unable to acquire clock 'spi'\n");
ret = PTR_ERR(sdd->clk); ret = PTR_ERR(sdd->clk);
goto err0; goto err_deref_master;
} }
if (clk_prepare_enable(sdd->clk)) { ret = clk_prepare_enable(sdd->clk);
if (ret) {
dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n"); dev_err(&pdev->dev, "Couldn't enable clock 'spi'\n");
ret = -EBUSY; goto err_deref_master;
goto err0;
} }
sprintf(clk_name, "spi_busclk%d", sci->src_clk_nr); sprintf(clk_name, "spi_busclk%d", sci->src_clk_nr);
...@@ -1143,13 +1149,28 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ...@@ -1143,13 +1149,28 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
dev_err(&pdev->dev, dev_err(&pdev->dev,
"Unable to acquire clock '%s'\n", clk_name); "Unable to acquire clock '%s'\n", clk_name);
ret = PTR_ERR(sdd->src_clk); ret = PTR_ERR(sdd->src_clk);
goto err2; goto err_disable_clk;
} }
if (clk_prepare_enable(sdd->src_clk)) { ret = clk_prepare_enable(sdd->src_clk);
if (ret) {
dev_err(&pdev->dev, "Couldn't enable clock '%s'\n", clk_name); dev_err(&pdev->dev, "Couldn't enable clock '%s'\n", clk_name);
ret = -EBUSY; goto err_disable_clk;
goto err2; }
if (sdd->port_conf->clk_ioclk) {
sdd->ioclk = devm_clk_get(&pdev->dev, "spi_ioclk");
if (IS_ERR(sdd->ioclk)) {
dev_err(&pdev->dev, "Unable to acquire 'ioclk'\n");
ret = PTR_ERR(sdd->ioclk);
goto err_disable_src_clk;
}
ret = clk_prepare_enable(sdd->ioclk);
if (ret) {
dev_err(&pdev->dev, "Couldn't enable clock 'ioclk'\n");
goto err_disable_src_clk;
}
} }
pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT); pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
...@@ -1169,7 +1190,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ...@@ -1169,7 +1190,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
if (ret != 0) { if (ret != 0) {
dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n", dev_err(&pdev->dev, "Failed to request IRQ %d: %d\n",
irq, ret); irq, ret);
goto err3; goto err_pm_put;
} }
writel(S3C64XX_SPI_INT_RX_OVERRUN_EN | S3C64XX_SPI_INT_RX_UNDERRUN_EN | writel(S3C64XX_SPI_INT_RX_OVERRUN_EN | S3C64XX_SPI_INT_RX_UNDERRUN_EN |
...@@ -1179,7 +1200,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ...@@ -1179,7 +1200,7 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
ret = devm_spi_register_master(&pdev->dev, master); ret = devm_spi_register_master(&pdev->dev, master);
if (ret != 0) { if (ret != 0) {
dev_err(&pdev->dev, "cannot register SPI master: %d\n", ret); dev_err(&pdev->dev, "cannot register SPI master: %d\n", ret);
goto err3; goto err_pm_put;
} }
dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d with %d Slaves attached\n", dev_dbg(&pdev->dev, "Samsung SoC SPI Driver loaded for Bus SPI-%d with %d Slaves attached\n",
...@@ -1193,15 +1214,17 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ...@@ -1193,15 +1214,17 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
return 0; return 0;
err3: err_pm_put:
pm_runtime_put_noidle(&pdev->dev); pm_runtime_put_noidle(&pdev->dev);
pm_runtime_disable(&pdev->dev); pm_runtime_disable(&pdev->dev);
pm_runtime_set_suspended(&pdev->dev); pm_runtime_set_suspended(&pdev->dev);
clk_disable_unprepare(sdd->ioclk);
err_disable_src_clk:
clk_disable_unprepare(sdd->src_clk); clk_disable_unprepare(sdd->src_clk);
err2: err_disable_clk:
clk_disable_unprepare(sdd->clk); clk_disable_unprepare(sdd->clk);
err0: err_deref_master:
spi_master_put(master); spi_master_put(master);
return ret; return ret;
...@@ -1209,13 +1232,15 @@ static int s3c64xx_spi_probe(struct platform_device *pdev) ...@@ -1209,13 +1232,15 @@ static int s3c64xx_spi_probe(struct platform_device *pdev)
static int s3c64xx_spi_remove(struct platform_device *pdev) static int s3c64xx_spi_remove(struct platform_device *pdev)
{ {
struct spi_master *master = spi_master_get(platform_get_drvdata(pdev)); struct spi_master *master = platform_get_drvdata(pdev);
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
pm_runtime_get_sync(&pdev->dev); pm_runtime_get_sync(&pdev->dev);
writel(0, sdd->regs + S3C64XX_SPI_INT_EN); writel(0, sdd->regs + S3C64XX_SPI_INT_EN);
clk_disable_unprepare(sdd->ioclk);
clk_disable_unprepare(sdd->src_clk); clk_disable_unprepare(sdd->src_clk);
clk_disable_unprepare(sdd->clk); clk_disable_unprepare(sdd->clk);
...@@ -1274,6 +1299,7 @@ static int s3c64xx_spi_runtime_suspend(struct device *dev) ...@@ -1274,6 +1299,7 @@ static int s3c64xx_spi_runtime_suspend(struct device *dev)
clk_disable_unprepare(sdd->clk); clk_disable_unprepare(sdd->clk);
clk_disable_unprepare(sdd->src_clk); clk_disable_unprepare(sdd->src_clk);
clk_disable_unprepare(sdd->ioclk);
return 0; return 0;
} }
...@@ -1284,17 +1310,28 @@ static int s3c64xx_spi_runtime_resume(struct device *dev) ...@@ -1284,17 +1310,28 @@ static int s3c64xx_spi_runtime_resume(struct device *dev)
struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master); struct s3c64xx_spi_driver_data *sdd = spi_master_get_devdata(master);
int ret; int ret;
ret = clk_prepare_enable(sdd->src_clk); if (sdd->port_conf->clk_ioclk) {
ret = clk_prepare_enable(sdd->ioclk);
if (ret != 0) if (ret != 0)
return ret; return ret;
}
ret = clk_prepare_enable(sdd->src_clk);
if (ret != 0)
goto err_disable_ioclk;
ret = clk_prepare_enable(sdd->clk); ret = clk_prepare_enable(sdd->clk);
if (ret != 0) { if (ret != 0)
clk_disable_unprepare(sdd->src_clk); goto err_disable_src_clk;
return ret;
}
return 0; return 0;
err_disable_src_clk:
clk_disable_unprepare(sdd->src_clk);
err_disable_ioclk:
clk_disable_unprepare(sdd->ioclk);
return ret;
} }
#endif /* CONFIG_PM */ #endif /* CONFIG_PM */
...@@ -1350,6 +1387,16 @@ static struct s3c64xx_spi_port_config exynos7_spi_port_config = { ...@@ -1350,6 +1387,16 @@ static struct s3c64xx_spi_port_config exynos7_spi_port_config = {
.quirks = S3C64XX_SPI_QUIRK_CS_AUTO, .quirks = S3C64XX_SPI_QUIRK_CS_AUTO,
}; };
static struct s3c64xx_spi_port_config exynos5433_spi_port_config = {
.fifo_lvl_mask = { 0x1ff, 0x7f, 0x7f, 0x7f, 0x7f, 0x1ff},
.rx_lvl_offset = 15,
.tx_st_done = 25,
.high_speed = true,
.clk_from_cmu = true,
.clk_ioclk = true,
.quirks = S3C64XX_SPI_QUIRK_CS_AUTO,
};
static const struct platform_device_id s3c64xx_spi_driver_ids[] = { static const struct platform_device_id s3c64xx_spi_driver_ids[] = {
{ {
.name = "s3c2443-spi", .name = "s3c2443-spi",
...@@ -1380,6 +1427,9 @@ static const struct of_device_id s3c64xx_spi_dt_match[] = { ...@@ -1380,6 +1427,9 @@ static const struct of_device_id s3c64xx_spi_dt_match[] = {
{ .compatible = "samsung,exynos7-spi", { .compatible = "samsung,exynos7-spi",
.data = (void *)&exynos7_spi_port_config, .data = (void *)&exynos7_spi_port_config,
}, },
{ .compatible = "samsung,exynos5433-spi",
.data = (void *)&exynos5433_spi_port_config,
},
{ }, { },
}; };
MODULE_DEVICE_TABLE(of, s3c64xx_spi_dt_match); MODULE_DEVICE_TABLE(of, s3c64xx_spi_dt_match);
......
...@@ -45,7 +45,6 @@ struct sh_msiof_spi_priv { ...@@ -45,7 +45,6 @@ struct sh_msiof_spi_priv {
void __iomem *mapbase; void __iomem *mapbase;
struct clk *clk; struct clk *clk;
struct platform_device *pdev; struct platform_device *pdev;
const struct sh_msiof_chipdata *chipdata;
struct sh_msiof_spi_info *info; struct sh_msiof_spi_info *info;
struct completion done; struct completion done;
unsigned int tx_fifo_size; unsigned int tx_fifo_size;
...@@ -271,7 +270,7 @@ static void sh_msiof_spi_set_clk_regs(struct sh_msiof_spi_priv *p, ...@@ -271,7 +270,7 @@ static void sh_msiof_spi_set_clk_regs(struct sh_msiof_spi_priv *p,
scr = sh_msiof_spi_div_table[k].brdv | SCR_BRPS(brps); scr = sh_msiof_spi_div_table[k].brdv | SCR_BRPS(brps);
sh_msiof_write(p, TSCR, scr); sh_msiof_write(p, TSCR, scr);
if (!(p->chipdata->master_flags & SPI_MASTER_MUST_TX)) if (!(p->master->flags & SPI_MASTER_MUST_TX))
sh_msiof_write(p, RSCR, scr); sh_msiof_write(p, RSCR, scr);
} }
...@@ -336,7 +335,7 @@ static void sh_msiof_spi_set_pin_regs(struct sh_msiof_spi_priv *p, ...@@ -336,7 +335,7 @@ static void sh_msiof_spi_set_pin_regs(struct sh_msiof_spi_priv *p,
tmp |= lsb_first << MDR1_BITLSB_SHIFT; tmp |= lsb_first << MDR1_BITLSB_SHIFT;
tmp |= sh_msiof_spi_get_dtdl_and_syncdl(p); tmp |= sh_msiof_spi_get_dtdl_and_syncdl(p);
sh_msiof_write(p, TMDR1, tmp | MDR1_TRMD | TMDR1_PCON); sh_msiof_write(p, TMDR1, tmp | MDR1_TRMD | TMDR1_PCON);
if (p->chipdata->master_flags & SPI_MASTER_MUST_TX) { if (p->master->flags & SPI_MASTER_MUST_TX) {
/* These bits are reserved if RX needs TX */ /* These bits are reserved if RX needs TX */
tmp &= ~0x0000ffff; tmp &= ~0x0000ffff;
} }
...@@ -360,7 +359,7 @@ static void sh_msiof_spi_set_mode_regs(struct sh_msiof_spi_priv *p, ...@@ -360,7 +359,7 @@ static void sh_msiof_spi_set_mode_regs(struct sh_msiof_spi_priv *p,
{ {
u32 dr2 = MDR2_BITLEN1(bits) | MDR2_WDLEN1(words); u32 dr2 = MDR2_BITLEN1(bits) | MDR2_WDLEN1(words);
if (tx_buf || (p->chipdata->master_flags & SPI_MASTER_MUST_TX)) if (tx_buf || (p->master->flags & SPI_MASTER_MUST_TX))
sh_msiof_write(p, TMDR2, dr2); sh_msiof_write(p, TMDR2, dr2);
else else
sh_msiof_write(p, TMDR2, dr2 | MDR2_GRPMASK1); sh_msiof_write(p, TMDR2, dr2 | MDR2_GRPMASK1);
...@@ -1152,6 +1151,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev) ...@@ -1152,6 +1151,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
{ {
struct resource *r; struct resource *r;
struct spi_master *master; struct spi_master *master;
const struct sh_msiof_chipdata *chipdata;
const struct of_device_id *of_id; const struct of_device_id *of_id;
struct sh_msiof_spi_priv *p; struct sh_msiof_spi_priv *p;
int i; int i;
...@@ -1170,10 +1170,10 @@ static int sh_msiof_spi_probe(struct platform_device *pdev) ...@@ -1170,10 +1170,10 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
of_id = of_match_device(sh_msiof_match, &pdev->dev); of_id = of_match_device(sh_msiof_match, &pdev->dev);
if (of_id) { if (of_id) {
p->chipdata = of_id->data; chipdata = of_id->data;
p->info = sh_msiof_spi_parse_dt(&pdev->dev); p->info = sh_msiof_spi_parse_dt(&pdev->dev);
} else { } else {
p->chipdata = (const void *)pdev->id_entry->driver_data; chipdata = (const void *)pdev->id_entry->driver_data;
p->info = dev_get_platdata(&pdev->dev); p->info = dev_get_platdata(&pdev->dev);
} }
...@@ -1217,8 +1217,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev) ...@@ -1217,8 +1217,8 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
pm_runtime_enable(&pdev->dev); pm_runtime_enable(&pdev->dev);
/* Platform data may override FIFO sizes */ /* Platform data may override FIFO sizes */
p->tx_fifo_size = p->chipdata->tx_fifo_size; p->tx_fifo_size = chipdata->tx_fifo_size;
p->rx_fifo_size = p->chipdata->rx_fifo_size; p->rx_fifo_size = chipdata->rx_fifo_size;
if (p->info->tx_fifo_override) if (p->info->tx_fifo_override)
p->tx_fifo_size = p->info->tx_fifo_override; p->tx_fifo_size = p->info->tx_fifo_override;
if (p->info->rx_fifo_override) if (p->info->rx_fifo_override)
...@@ -1227,7 +1227,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev) ...@@ -1227,7 +1227,7 @@ static int sh_msiof_spi_probe(struct platform_device *pdev)
/* init master code */ /* init master code */
master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH; master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;
master->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE; master->mode_bits |= SPI_LSB_FIRST | SPI_3WIRE;
master->flags = p->chipdata->master_flags; master->flags = chipdata->master_flags;
master->bus_num = pdev->id; master->bus_num = pdev->id;
master->dev.of_node = pdev->dev.of_node; master->dev.of_node = pdev->dev.of_node;
master->num_chipselect = p->info->num_chipselect; master->num_chipselect = p->info->num_chipselect;
......
...@@ -82,7 +82,6 @@ struct spi_sh_data { ...@@ -82,7 +82,6 @@ struct spi_sh_data {
int irq; int irq;
struct spi_master *master; struct spi_master *master;
struct list_head queue; struct list_head queue;
struct workqueue_struct *workqueue;
struct work_struct ws; struct work_struct ws;
unsigned long cr1; unsigned long cr1;
wait_queue_head_t wait; wait_queue_head_t wait;
...@@ -380,7 +379,7 @@ static int spi_sh_transfer(struct spi_device *spi, struct spi_message *mesg) ...@@ -380,7 +379,7 @@ static int spi_sh_transfer(struct spi_device *spi, struct spi_message *mesg)
spi_sh_clear_bit(ss, SPI_SH_SSA, SPI_SH_CR1); spi_sh_clear_bit(ss, SPI_SH_SSA, SPI_SH_CR1);
list_add_tail(&mesg->queue, &ss->queue); list_add_tail(&mesg->queue, &ss->queue);
queue_work(ss->workqueue, &ss->ws); schedule_work(&ss->ws);
spin_unlock_irqrestore(&ss->lock, flags); spin_unlock_irqrestore(&ss->lock, flags);
...@@ -425,7 +424,7 @@ static int spi_sh_remove(struct platform_device *pdev) ...@@ -425,7 +424,7 @@ static int spi_sh_remove(struct platform_device *pdev)
struct spi_sh_data *ss = platform_get_drvdata(pdev); struct spi_sh_data *ss = platform_get_drvdata(pdev);
spi_unregister_master(ss->master); spi_unregister_master(ss->master);
destroy_workqueue(ss->workqueue); flush_work(&ss->ws);
free_irq(ss->irq, ss); free_irq(ss->irq, ss);
return 0; return 0;
...@@ -484,18 +483,11 @@ static int spi_sh_probe(struct platform_device *pdev) ...@@ -484,18 +483,11 @@ static int spi_sh_probe(struct platform_device *pdev)
spin_lock_init(&ss->lock); spin_lock_init(&ss->lock);
INIT_WORK(&ss->ws, spi_sh_work); INIT_WORK(&ss->ws, spi_sh_work);
init_waitqueue_head(&ss->wait); init_waitqueue_head(&ss->wait);
ss->workqueue = create_singlethread_workqueue(
dev_name(master->dev.parent));
if (ss->workqueue == NULL) {
dev_err(&pdev->dev, "create workqueue error\n");
ret = -EBUSY;
goto error1;
}
ret = request_irq(irq, spi_sh_irq, 0, "spi_sh", ss); ret = request_irq(irq, spi_sh_irq, 0, "spi_sh", ss);
if (ret < 0) { if (ret < 0) {
dev_err(&pdev->dev, "request_irq error\n"); dev_err(&pdev->dev, "request_irq error\n");
goto error2; goto error1;
} }
master->num_chipselect = 2; master->num_chipselect = 2;
...@@ -514,8 +506,6 @@ static int spi_sh_probe(struct platform_device *pdev) ...@@ -514,8 +506,6 @@ static int spi_sh_probe(struct platform_device *pdev)
error3: error3:
free_irq(irq, ss); free_irq(irq, ss);
error2:
destroy_workqueue(ss->workqueue);
error1: error1:
spi_master_put(master); spi_master_put(master);
......
...@@ -38,6 +38,7 @@ struct s3c64xx_spi_csinfo { ...@@ -38,6 +38,7 @@ struct s3c64xx_spi_csinfo {
struct s3c64xx_spi_info { struct s3c64xx_spi_info {
int src_clk_nr; int src_clk_nr;
int num_cs; int num_cs;
bool no_cs;
int (*cfg_gpio)(void); int (*cfg_gpio)(void);
dma_filter_fn filter; dma_filter_fn filter;
void *dma_tx; void *dma_tx;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment