summaryrefslogtreecommitdiff
path: root/drivers/spi/spi-pxa2xx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/spi/spi-pxa2xx.c')
-rw-r--r--drivers/spi/spi-pxa2xx.c153
1 files changed, 110 insertions, 43 deletions
diff --git a/drivers/spi/spi-pxa2xx.c b/drivers/spi/spi-pxa2xx.c
index ab9914ad8..86138e410 100644
--- a/drivers/spi/spi-pxa2xx.c
+++ b/drivers/spi/spi-pxa2xx.c
@@ -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,12 +108,25 @@ 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,
.reg_ssp = 0x20,
.reg_cs_ctrl = 0x24,
- .reg_capabilities = 0xfc,
+ .reg_capabilities = -1,
.rx_threshold = 1,
.tx_threshold_lo = 32,
.tx_threshold_hi = 56,
@@ -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;