summaryrefslogtreecommitdiffstats
path: root/drivers/spi/spi-pxa2xx.c
diff options
context:
space:
mode:
authorMika Westerberg <mika.westerberg@linux.intel.com>2013-01-22 12:26:32 +0200
committerMark Brown <broonie@opensource.wolfsonmicro.com>2013-02-08 13:14:40 +0000
commita0d2642e9296882cda3ad03ff3d9a6649cd70439 (patch)
tree4c3f4a29ba904a357789cd7c2286eb035fa16340 /drivers/spi/spi-pxa2xx.c
parentb833172fd8f44fb56e0b3cb810155a6baecc65dc (diff)
downloadlinux-a0d2642e9296882cda3ad03ff3d9a6649cd70439.tar.bz2
spi/pxa2xx: add support for Intel Low Power Subsystem SPI
Intel LPSS SPI is pretty much the same as the PXA27xx SPI except that it has few additional features over the original: o FIFO depth is 256 entries o RX FIFO has one watermark o TX FIFO has two watermarks, low and high o chip select can be controlled by writing to a register The new FIFO registers follow immediately the PXA27xx registers but then there are some additional LPSS private registers at offset 1k or 2k from the base address. For these private registers we add new accessors that take advantage of drv_data->lpss_base once it is resolved. We add a new type LPSS_SSP that can be used to distinguish the LPSS devices from others. Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com> Tested-by: Lu Cao <lucao@marvell.com> Signed-off-by: Mark Brown <broonie@opensource.wolfsonmicro.com>
Diffstat (limited to 'drivers/spi/spi-pxa2xx.c')
-rw-r--r--drivers/spi/spi-pxa2xx.c133
1 files changed, 129 insertions, 4 deletions
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index 8a5ba0aa095a..4bd6b729f710 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2005 Stephen Street / StreetFire Sound Labs
+ * Copyright (C) 2013, Intel Corporation
*
* 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
@@ -61,6 +62,98 @@ MODULE_ALIAS("platform:pxa2xx-spi");
| SSCR1_RFT | SSCR1_TFT | SSCR1_MWDS \
| SSCR1_SPH | SSCR1_SPO | SSCR1_LBM)
+#define LPSS_RX_THRESH_DFLT 64
+#define LPSS_TX_LOTHRESH_DFLT 160
+#define LPSS_TX_HITHRESH_DFLT 224
+
+/* Offset from drv_data->lpss_base */
+#define SPI_CS_CONTROL 0x18
+#define SPI_CS_CONTROL_SW_MODE BIT(0)
+#define SPI_CS_CONTROL_CS_HIGH BIT(1)
+
+static bool is_lpss_ssp(const struct driver_data *drv_data)
+{
+ return drv_data->ssp_type == LPSS_SSP;
+}
+
+/*
+ * Read and write LPSS SSP private registers. Caller must first check that
+ * is_lpss_ssp() returns true before these can be called.
+ */
+static u32 __lpss_ssp_read_priv(struct driver_data *drv_data, unsigned offset)
+{
+ WARN_ON(!drv_data->lpss_base);
+ return readl(drv_data->lpss_base + offset);
+}
+
+static void __lpss_ssp_write_priv(struct driver_data *drv_data,
+ unsigned offset, u32 value)
+{
+ WARN_ON(!drv_data->lpss_base);
+ writel(value, drv_data->lpss_base + offset);
+}
+
+/*
+ * lpss_ssp_setup - perform LPSS SSP specific setup
+ * @drv_data: pointer to the driver private data
+ *
+ * Perform LPSS SSP specific setup. This function must be called first if
+ * one is going to use LPSS SSP private registers.
+ */
+static void lpss_ssp_setup(struct driver_data *drv_data)
+{
+ unsigned offset = 0x400;
+ u32 value, orig;
+
+ if (!is_lpss_ssp(drv_data))
+ return;
+
+ /*
+ * Perform auto-detection of the LPSS SSP private registers. They
+ * can be either at 1k or 2k offset from the base address.
+ */
+ orig = readl(drv_data->ioaddr + offset + SPI_CS_CONTROL);
+
+ value = orig | SPI_CS_CONTROL_SW_MODE;
+ writel(value, drv_data->ioaddr + offset + SPI_CS_CONTROL);
+ value = readl(drv_data->ioaddr + offset + SPI_CS_CONTROL);
+ if (value != (orig | SPI_CS_CONTROL_SW_MODE)) {
+ offset = 0x800;
+ goto detection_done;
+ }
+
+ value &= ~SPI_CS_CONTROL_SW_MODE;
+ writel(value, drv_data->ioaddr + offset + SPI_CS_CONTROL);
+ value = readl(drv_data->ioaddr + offset + SPI_CS_CONTROL);
+ if (value != orig) {
+ offset = 0x800;
+ goto detection_done;
+ }
+
+detection_done:
+ /* Now set the LPSS base */
+ drv_data->lpss_base = drv_data->ioaddr + offset;
+
+ /* Enable software chip select control */
+ value = SPI_CS_CONTROL_SW_MODE | SPI_CS_CONTROL_CS_HIGH;
+ __lpss_ssp_write_priv(drv_data, SPI_CS_CONTROL, value);
+}
+
+static void lpss_ssp_cs_control(struct driver_data *drv_data, bool enable)
+{
+ u32 value;
+
+ if (!is_lpss_ssp(drv_data))
+ return;
+
+ value = __lpss_ssp_read_priv(drv_data, SPI_CS_CONTROL);
+ if (enable)
+ value &= ~SPI_CS_CONTROL_CS_HIGH;
+ else
+ value |= SPI_CS_CONTROL_CS_HIGH;
+ __lpss_ssp_write_priv(drv_data, SPI_CS_CONTROL, value);
+}
+
static void cs_assert(struct driver_data *drv_data)
{
struct chip_data *chip = drv_data->cur_chip;
@@ -75,8 +168,12 @@ static void cs_assert(struct driver_data *drv_data)
return;
}
- if (gpio_is_valid(chip->gpio_cs))
+ if (gpio_is_valid(chip->gpio_cs)) {
gpio_set_value(chip->gpio_cs, chip->gpio_cs_inverted);
+ return;
+ }
+
+ lpss_ssp_cs_control(drv_data, true);
}
static void cs_deassert(struct driver_data *drv_data)
@@ -91,8 +188,12 @@ static void cs_deassert(struct driver_data *drv_data)
return;
}
- if (gpio_is_valid(chip->gpio_cs))
+ if (gpio_is_valid(chip->gpio_cs)) {
gpio_set_value(chip->gpio_cs, !chip->gpio_cs_inverted);
+ return;
+ }
+
+ lpss_ssp_cs_control(drv_data, false);
}
int pxa2xx_spi_flush(struct driver_data *drv_data)
@@ -642,6 +743,13 @@ static void pump_transfers(unsigned long data)
write_SSSR_CS(drv_data, drv_data->clear_sr);
}
+ if (is_lpss_ssp(drv_data)) {
+ if ((read_SSIRF(reg) & 0xff) != chip->lpss_rx_threshold)
+ write_SSIRF(chip->lpss_rx_threshold, reg);
+ if ((read_SSITF(reg) & 0xffff) != chip->lpss_tx_threshold)
+ write_SSITF(chip->lpss_tx_threshold, reg);
+ }
+
/* see if we need to reload the config registers */
if ((read_SSCR0(reg) != cr0)
|| (read_SSCR1(reg) & SSCR1_CHANGE_MASK) !=
@@ -754,8 +862,17 @@ static int setup(struct spi_device *spi)
struct chip_data *chip;
struct driver_data *drv_data = spi_master_get_devdata(spi->master);
unsigned int clk_div;
- uint tx_thres = TX_THRESH_DFLT;
- uint rx_thres = RX_THRESH_DFLT;
+ uint tx_thres, tx_hi_thres, rx_thres;
+
+ if (is_lpss_ssp(drv_data)) {
+ tx_thres = LPSS_TX_LOTHRESH_DFLT;
+ tx_hi_thres = LPSS_TX_HITHRESH_DFLT;
+ rx_thres = LPSS_RX_THRESH_DFLT;
+ } else {
+ tx_thres = TX_THRESH_DFLT;
+ tx_hi_thres = 0;
+ rx_thres = RX_THRESH_DFLT;
+ }
if (!pxa25x_ssp_comp(drv_data)
&& (spi->bits_per_word < 4 || spi->bits_per_word > 32)) {
@@ -808,6 +925,8 @@ static int setup(struct spi_device *spi)
chip->timeout = chip_info->timeout;
if (chip_info->tx_threshold)
tx_thres = chip_info->tx_threshold;
+ if (chip_info->tx_hi_threshold)
+ tx_hi_thres = chip_info->tx_hi_threshold;
if (chip_info->rx_threshold)
rx_thres = chip_info->rx_threshold;
chip->enable_dma = drv_data->master_info->enable_dma;
@@ -819,6 +938,10 @@ static int setup(struct spi_device *spi)
chip->threshold = (SSCR1_RxTresh(rx_thres) & SSCR1_RFT) |
(SSCR1_TxTresh(tx_thres) & SSCR1_TFT);
+ chip->lpss_rx_threshold = SSIRF_RxThresh(rx_thres);
+ chip->lpss_tx_threshold = SSITF_TxLoThresh(tx_thres)
+ | SSITF_TxHiThresh(tx_hi_thres);
+
/* set dma burst and threshold outside of chip_info path so that if
* chip_info goes away after setting chip->enable_dma, the
* burst and threshold can still respond to changes in bits_per_word */
@@ -1006,6 +1129,8 @@ static int pxa2xx_spi_probe(struct platform_device *pdev)
write_SSTO(0, drv_data->ioaddr);
write_SSPSP(0, drv_data->ioaddr);
+ lpss_ssp_setup(drv_data);
+
tasklet_init(&drv_data->pump_transfers, pump_transfers,
(unsigned long)drv_data);