mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 11:19:37 +00:00
![Thomas Gleixner](/assets/img/avatar_default.png)
Based on 1 normalized pattern(s): 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 extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 3029 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
843 lines
21 KiB
C
843 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Freescale eSPI controller driver.
|
|
*
|
|
* Copyright 2010 Freescale Semiconductor, Inc.
|
|
*/
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/fsl_devices.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <sysdev/fsl_soc.h>
|
|
|
|
/* eSPI Controller registers */
|
|
#define ESPI_SPMODE 0x00 /* eSPI mode register */
|
|
#define ESPI_SPIE 0x04 /* eSPI event register */
|
|
#define ESPI_SPIM 0x08 /* eSPI mask register */
|
|
#define ESPI_SPCOM 0x0c /* eSPI command register */
|
|
#define ESPI_SPITF 0x10 /* eSPI transmit FIFO access register*/
|
|
#define ESPI_SPIRF 0x14 /* eSPI receive FIFO access register*/
|
|
#define ESPI_SPMODE0 0x20 /* eSPI cs0 mode register */
|
|
|
|
#define ESPI_SPMODEx(x) (ESPI_SPMODE0 + (x) * 4)
|
|
|
|
/* eSPI Controller mode register definitions */
|
|
#define SPMODE_ENABLE BIT(31)
|
|
#define SPMODE_LOOP BIT(30)
|
|
#define SPMODE_TXTHR(x) ((x) << 8)
|
|
#define SPMODE_RXTHR(x) ((x) << 0)
|
|
|
|
/* eSPI Controller CS mode register definitions */
|
|
#define CSMODE_CI_INACTIVEHIGH BIT(31)
|
|
#define CSMODE_CP_BEGIN_EDGECLK BIT(30)
|
|
#define CSMODE_REV BIT(29)
|
|
#define CSMODE_DIV16 BIT(28)
|
|
#define CSMODE_PM(x) ((x) << 24)
|
|
#define CSMODE_POL_1 BIT(20)
|
|
#define CSMODE_LEN(x) ((x) << 16)
|
|
#define CSMODE_BEF(x) ((x) << 12)
|
|
#define CSMODE_AFT(x) ((x) << 8)
|
|
#define CSMODE_CG(x) ((x) << 3)
|
|
|
|
#define FSL_ESPI_FIFO_SIZE 32
|
|
#define FSL_ESPI_RXTHR 15
|
|
|
|
/* Default mode/csmode for eSPI controller */
|
|
#define SPMODE_INIT_VAL (SPMODE_TXTHR(4) | SPMODE_RXTHR(FSL_ESPI_RXTHR))
|
|
#define CSMODE_INIT_VAL (CSMODE_POL_1 | CSMODE_BEF(0) \
|
|
| CSMODE_AFT(0) | CSMODE_CG(1))
|
|
|
|
/* SPIE register values */
|
|
#define SPIE_RXCNT(reg) ((reg >> 24) & 0x3F)
|
|
#define SPIE_TXCNT(reg) ((reg >> 16) & 0x3F)
|
|
#define SPIE_TXE BIT(15) /* TX FIFO empty */
|
|
#define SPIE_DON BIT(14) /* TX done */
|
|
#define SPIE_RXT BIT(13) /* RX FIFO threshold */
|
|
#define SPIE_RXF BIT(12) /* RX FIFO full */
|
|
#define SPIE_TXT BIT(11) /* TX FIFO threshold*/
|
|
#define SPIE_RNE BIT(9) /* RX FIFO not empty */
|
|
#define SPIE_TNF BIT(8) /* TX FIFO not full */
|
|
|
|
/* SPIM register values */
|
|
#define SPIM_TXE BIT(15) /* TX FIFO empty */
|
|
#define SPIM_DON BIT(14) /* TX done */
|
|
#define SPIM_RXT BIT(13) /* RX FIFO threshold */
|
|
#define SPIM_RXF BIT(12) /* RX FIFO full */
|
|
#define SPIM_TXT BIT(11) /* TX FIFO threshold*/
|
|
#define SPIM_RNE BIT(9) /* RX FIFO not empty */
|
|
#define SPIM_TNF BIT(8) /* TX FIFO not full */
|
|
|
|
/* SPCOM register values */
|
|
#define SPCOM_CS(x) ((x) << 30)
|
|
#define SPCOM_DO BIT(28) /* Dual output */
|
|
#define SPCOM_TO BIT(27) /* TX only */
|
|
#define SPCOM_RXSKIP(x) ((x) << 16)
|
|
#define SPCOM_TRANLEN(x) ((x) << 0)
|
|
|
|
#define SPCOM_TRANLEN_MAX 0x10000 /* Max transaction length */
|
|
|
|
#define AUTOSUSPEND_TIMEOUT 2000
|
|
|
|
struct fsl_espi {
|
|
struct device *dev;
|
|
void __iomem *reg_base;
|
|
|
|
struct list_head *m_transfers;
|
|
struct spi_transfer *tx_t;
|
|
unsigned int tx_pos;
|
|
bool tx_done;
|
|
struct spi_transfer *rx_t;
|
|
unsigned int rx_pos;
|
|
bool rx_done;
|
|
|
|
bool swab;
|
|
unsigned int rxskip;
|
|
|
|
spinlock_t lock;
|
|
|
|
u32 spibrg; /* SPIBRG input clock */
|
|
|
|
struct completion done;
|
|
};
|
|
|
|
struct fsl_espi_cs {
|
|
u32 hw_mode;
|
|
};
|
|
|
|
static inline u32 fsl_espi_read_reg(struct fsl_espi *espi, int offset)
|
|
{
|
|
return ioread32be(espi->reg_base + offset);
|
|
}
|
|
|
|
static inline u16 fsl_espi_read_reg16(struct fsl_espi *espi, int offset)
|
|
{
|
|
return ioread16be(espi->reg_base + offset);
|
|
}
|
|
|
|
static inline u8 fsl_espi_read_reg8(struct fsl_espi *espi, int offset)
|
|
{
|
|
return ioread8(espi->reg_base + offset);
|
|
}
|
|
|
|
static inline void fsl_espi_write_reg(struct fsl_espi *espi, int offset,
|
|
u32 val)
|
|
{
|
|
iowrite32be(val, espi->reg_base + offset);
|
|
}
|
|
|
|
static inline void fsl_espi_write_reg16(struct fsl_espi *espi, int offset,
|
|
u16 val)
|
|
{
|
|
iowrite16be(val, espi->reg_base + offset);
|
|
}
|
|
|
|
static inline void fsl_espi_write_reg8(struct fsl_espi *espi, int offset,
|
|
u8 val)
|
|
{
|
|
iowrite8(val, espi->reg_base + offset);
|
|
}
|
|
|
|
static int fsl_espi_check_message(struct spi_message *m)
|
|
{
|
|
struct fsl_espi *espi = spi_master_get_devdata(m->spi->master);
|
|
struct spi_transfer *t, *first;
|
|
|
|
if (m->frame_length > SPCOM_TRANLEN_MAX) {
|
|
dev_err(espi->dev, "message too long, size is %u bytes\n",
|
|
m->frame_length);
|
|
return -EMSGSIZE;
|
|
}
|
|
|
|
first = list_first_entry(&m->transfers, struct spi_transfer,
|
|
transfer_list);
|
|
|
|
list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
if (first->bits_per_word != t->bits_per_word ||
|
|
first->speed_hz != t->speed_hz) {
|
|
dev_err(espi->dev, "bits_per_word/speed_hz should be the same for all transfers\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* ESPI supports MSB-first transfers for word size 8 / 16 only */
|
|
if (!(m->spi->mode & SPI_LSB_FIRST) && first->bits_per_word != 8 &&
|
|
first->bits_per_word != 16) {
|
|
dev_err(espi->dev,
|
|
"MSB-first transfer not supported for wordsize %u\n",
|
|
first->bits_per_word);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int fsl_espi_check_rxskip_mode(struct spi_message *m)
|
|
{
|
|
struct spi_transfer *t;
|
|
unsigned int i = 0, rxskip = 0;
|
|
|
|
/*
|
|
* prerequisites for ESPI rxskip mode:
|
|
* - message has two transfers
|
|
* - first transfer is a write and second is a read
|
|
*
|
|
* In addition the current low-level transfer mechanism requires
|
|
* that the rxskip bytes fit into the TX FIFO. Else the transfer
|
|
* would hang because after the first FSL_ESPI_FIFO_SIZE bytes
|
|
* the TX FIFO isn't re-filled.
|
|
*/
|
|
list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
if (i == 0) {
|
|
if (!t->tx_buf || t->rx_buf ||
|
|
t->len > FSL_ESPI_FIFO_SIZE)
|
|
return 0;
|
|
rxskip = t->len;
|
|
} else if (i == 1) {
|
|
if (t->tx_buf || !t->rx_buf)
|
|
return 0;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
return i == 2 ? rxskip : 0;
|
|
}
|
|
|
|
static void fsl_espi_fill_tx_fifo(struct fsl_espi *espi, u32 events)
|
|
{
|
|
u32 tx_fifo_avail;
|
|
unsigned int tx_left;
|
|
const void *tx_buf;
|
|
|
|
/* if events is zero transfer has not started and tx fifo is empty */
|
|
tx_fifo_avail = events ? SPIE_TXCNT(events) : FSL_ESPI_FIFO_SIZE;
|
|
start:
|
|
tx_left = espi->tx_t->len - espi->tx_pos;
|
|
tx_buf = espi->tx_t->tx_buf;
|
|
while (tx_fifo_avail >= min(4U, tx_left) && tx_left) {
|
|
if (tx_left >= 4) {
|
|
if (!tx_buf)
|
|
fsl_espi_write_reg(espi, ESPI_SPITF, 0);
|
|
else if (espi->swab)
|
|
fsl_espi_write_reg(espi, ESPI_SPITF,
|
|
swahb32p(tx_buf + espi->tx_pos));
|
|
else
|
|
fsl_espi_write_reg(espi, ESPI_SPITF,
|
|
*(u32 *)(tx_buf + espi->tx_pos));
|
|
espi->tx_pos += 4;
|
|
tx_left -= 4;
|
|
tx_fifo_avail -= 4;
|
|
} else if (tx_left >= 2 && tx_buf && espi->swab) {
|
|
fsl_espi_write_reg16(espi, ESPI_SPITF,
|
|
swab16p(tx_buf + espi->tx_pos));
|
|
espi->tx_pos += 2;
|
|
tx_left -= 2;
|
|
tx_fifo_avail -= 2;
|
|
} else {
|
|
if (!tx_buf)
|
|
fsl_espi_write_reg8(espi, ESPI_SPITF, 0);
|
|
else
|
|
fsl_espi_write_reg8(espi, ESPI_SPITF,
|
|
*(u8 *)(tx_buf + espi->tx_pos));
|
|
espi->tx_pos += 1;
|
|
tx_left -= 1;
|
|
tx_fifo_avail -= 1;
|
|
}
|
|
}
|
|
|
|
if (!tx_left) {
|
|
/* Last transfer finished, in rxskip mode only one is needed */
|
|
if (list_is_last(&espi->tx_t->transfer_list,
|
|
espi->m_transfers) || espi->rxskip) {
|
|
espi->tx_done = true;
|
|
return;
|
|
}
|
|
espi->tx_t = list_next_entry(espi->tx_t, transfer_list);
|
|
espi->tx_pos = 0;
|
|
/* continue with next transfer if tx fifo is not full */
|
|
if (tx_fifo_avail)
|
|
goto start;
|
|
}
|
|
}
|
|
|
|
static void fsl_espi_read_rx_fifo(struct fsl_espi *espi, u32 events)
|
|
{
|
|
u32 rx_fifo_avail = SPIE_RXCNT(events);
|
|
unsigned int rx_left;
|
|
void *rx_buf;
|
|
|
|
start:
|
|
rx_left = espi->rx_t->len - espi->rx_pos;
|
|
rx_buf = espi->rx_t->rx_buf;
|
|
while (rx_fifo_avail >= min(4U, rx_left) && rx_left) {
|
|
if (rx_left >= 4) {
|
|
u32 val = fsl_espi_read_reg(espi, ESPI_SPIRF);
|
|
|
|
if (rx_buf && espi->swab)
|
|
*(u32 *)(rx_buf + espi->rx_pos) = swahb32(val);
|
|
else if (rx_buf)
|
|
*(u32 *)(rx_buf + espi->rx_pos) = val;
|
|
espi->rx_pos += 4;
|
|
rx_left -= 4;
|
|
rx_fifo_avail -= 4;
|
|
} else if (rx_left >= 2 && rx_buf && espi->swab) {
|
|
u16 val = fsl_espi_read_reg16(espi, ESPI_SPIRF);
|
|
|
|
*(u16 *)(rx_buf + espi->rx_pos) = swab16(val);
|
|
espi->rx_pos += 2;
|
|
rx_left -= 2;
|
|
rx_fifo_avail -= 2;
|
|
} else {
|
|
u8 val = fsl_espi_read_reg8(espi, ESPI_SPIRF);
|
|
|
|
if (rx_buf)
|
|
*(u8 *)(rx_buf + espi->rx_pos) = val;
|
|
espi->rx_pos += 1;
|
|
rx_left -= 1;
|
|
rx_fifo_avail -= 1;
|
|
}
|
|
}
|
|
|
|
if (!rx_left) {
|
|
if (list_is_last(&espi->rx_t->transfer_list,
|
|
espi->m_transfers)) {
|
|
espi->rx_done = true;
|
|
return;
|
|
}
|
|
espi->rx_t = list_next_entry(espi->rx_t, transfer_list);
|
|
espi->rx_pos = 0;
|
|
/* continue with next transfer if rx fifo is not empty */
|
|
if (rx_fifo_avail)
|
|
goto start;
|
|
}
|
|
}
|
|
|
|
static void fsl_espi_setup_transfer(struct spi_device *spi,
|
|
struct spi_transfer *t)
|
|
{
|
|
struct fsl_espi *espi = spi_master_get_devdata(spi->master);
|
|
int bits_per_word = t ? t->bits_per_word : spi->bits_per_word;
|
|
u32 pm, hz = t ? t->speed_hz : spi->max_speed_hz;
|
|
struct fsl_espi_cs *cs = spi_get_ctldata(spi);
|
|
u32 hw_mode_old = cs->hw_mode;
|
|
|
|
/* mask out bits we are going to set */
|
|
cs->hw_mode &= ~(CSMODE_LEN(0xF) | CSMODE_DIV16 | CSMODE_PM(0xF));
|
|
|
|
cs->hw_mode |= CSMODE_LEN(bits_per_word - 1);
|
|
|
|
pm = DIV_ROUND_UP(espi->spibrg, hz * 4) - 1;
|
|
|
|
if (pm > 15) {
|
|
cs->hw_mode |= CSMODE_DIV16;
|
|
pm = DIV_ROUND_UP(espi->spibrg, hz * 16 * 4) - 1;
|
|
}
|
|
|
|
cs->hw_mode |= CSMODE_PM(pm);
|
|
|
|
/* don't write the mode register if the mode doesn't change */
|
|
if (cs->hw_mode != hw_mode_old)
|
|
fsl_espi_write_reg(espi, ESPI_SPMODEx(spi->chip_select),
|
|
cs->hw_mode);
|
|
}
|
|
|
|
static int fsl_espi_bufs(struct spi_device *spi, struct spi_transfer *t)
|
|
{
|
|
struct fsl_espi *espi = spi_master_get_devdata(spi->master);
|
|
unsigned int rx_len = t->len;
|
|
u32 mask, spcom;
|
|
int ret;
|
|
|
|
reinit_completion(&espi->done);
|
|
|
|
/* Set SPCOM[CS] and SPCOM[TRANLEN] field */
|
|
spcom = SPCOM_CS(spi->chip_select);
|
|
spcom |= SPCOM_TRANLEN(t->len - 1);
|
|
|
|
/* configure RXSKIP mode */
|
|
if (espi->rxskip) {
|
|
spcom |= SPCOM_RXSKIP(espi->rxskip);
|
|
rx_len = t->len - espi->rxskip;
|
|
if (t->rx_nbits == SPI_NBITS_DUAL)
|
|
spcom |= SPCOM_DO;
|
|
}
|
|
|
|
fsl_espi_write_reg(espi, ESPI_SPCOM, spcom);
|
|
|
|
/* enable interrupts */
|
|
mask = SPIM_DON;
|
|
if (rx_len > FSL_ESPI_FIFO_SIZE)
|
|
mask |= SPIM_RXT;
|
|
fsl_espi_write_reg(espi, ESPI_SPIM, mask);
|
|
|
|
/* Prevent filling the fifo from getting interrupted */
|
|
spin_lock_irq(&espi->lock);
|
|
fsl_espi_fill_tx_fifo(espi, 0);
|
|
spin_unlock_irq(&espi->lock);
|
|
|
|
/* Won't hang up forever, SPI bus sometimes got lost interrupts... */
|
|
ret = wait_for_completion_timeout(&espi->done, 2 * HZ);
|
|
if (ret == 0)
|
|
dev_err(espi->dev, "Transfer timed out!\n");
|
|
|
|
/* disable rx ints */
|
|
fsl_espi_write_reg(espi, ESPI_SPIM, 0);
|
|
|
|
return ret == 0 ? -ETIMEDOUT : 0;
|
|
}
|
|
|
|
static int fsl_espi_trans(struct spi_message *m, struct spi_transfer *trans)
|
|
{
|
|
struct fsl_espi *espi = spi_master_get_devdata(m->spi->master);
|
|
struct spi_device *spi = m->spi;
|
|
int ret;
|
|
|
|
/* In case of LSB-first and bits_per_word > 8 byte-swap all words */
|
|
espi->swab = spi->mode & SPI_LSB_FIRST && trans->bits_per_word > 8;
|
|
|
|
espi->m_transfers = &m->transfers;
|
|
espi->tx_t = list_first_entry(&m->transfers, struct spi_transfer,
|
|
transfer_list);
|
|
espi->tx_pos = 0;
|
|
espi->tx_done = false;
|
|
espi->rx_t = list_first_entry(&m->transfers, struct spi_transfer,
|
|
transfer_list);
|
|
espi->rx_pos = 0;
|
|
espi->rx_done = false;
|
|
|
|
espi->rxskip = fsl_espi_check_rxskip_mode(m);
|
|
if (trans->rx_nbits == SPI_NBITS_DUAL && !espi->rxskip) {
|
|
dev_err(espi->dev, "Dual output mode requires RXSKIP mode!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* In RXSKIP mode skip first transfer for reads */
|
|
if (espi->rxskip)
|
|
espi->rx_t = list_next_entry(espi->rx_t, transfer_list);
|
|
|
|
fsl_espi_setup_transfer(spi, trans);
|
|
|
|
ret = fsl_espi_bufs(spi, trans);
|
|
|
|
if (trans->delay_usecs)
|
|
udelay(trans->delay_usecs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fsl_espi_do_one_msg(struct spi_master *master,
|
|
struct spi_message *m)
|
|
{
|
|
unsigned int delay_usecs = 0, rx_nbits = 0;
|
|
struct spi_transfer *t, trans = {};
|
|
int ret;
|
|
|
|
ret = fsl_espi_check_message(m);
|
|
if (ret)
|
|
goto out;
|
|
|
|
list_for_each_entry(t, &m->transfers, transfer_list) {
|
|
if (t->delay_usecs > delay_usecs)
|
|
delay_usecs = t->delay_usecs;
|
|
if (t->rx_nbits > rx_nbits)
|
|
rx_nbits = t->rx_nbits;
|
|
}
|
|
|
|
t = list_first_entry(&m->transfers, struct spi_transfer,
|
|
transfer_list);
|
|
|
|
trans.len = m->frame_length;
|
|
trans.speed_hz = t->speed_hz;
|
|
trans.bits_per_word = t->bits_per_word;
|
|
trans.delay_usecs = delay_usecs;
|
|
trans.rx_nbits = rx_nbits;
|
|
|
|
if (trans.len)
|
|
ret = fsl_espi_trans(m, &trans);
|
|
|
|
m->actual_length = ret ? 0 : trans.len;
|
|
out:
|
|
if (m->status == -EINPROGRESS)
|
|
m->status = ret;
|
|
|
|
spi_finalize_current_message(master);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int fsl_espi_setup(struct spi_device *spi)
|
|
{
|
|
struct fsl_espi *espi;
|
|
u32 loop_mode;
|
|
struct fsl_espi_cs *cs = spi_get_ctldata(spi);
|
|
|
|
if (!cs) {
|
|
cs = kzalloc(sizeof(*cs), GFP_KERNEL);
|
|
if (!cs)
|
|
return -ENOMEM;
|
|
spi_set_ctldata(spi, cs);
|
|
}
|
|
|
|
espi = spi_master_get_devdata(spi->master);
|
|
|
|
pm_runtime_get_sync(espi->dev);
|
|
|
|
cs->hw_mode = fsl_espi_read_reg(espi, ESPI_SPMODEx(spi->chip_select));
|
|
/* mask out bits we are going to set */
|
|
cs->hw_mode &= ~(CSMODE_CP_BEGIN_EDGECLK | CSMODE_CI_INACTIVEHIGH
|
|
| CSMODE_REV);
|
|
|
|
if (spi->mode & SPI_CPHA)
|
|
cs->hw_mode |= CSMODE_CP_BEGIN_EDGECLK;
|
|
if (spi->mode & SPI_CPOL)
|
|
cs->hw_mode |= CSMODE_CI_INACTIVEHIGH;
|
|
if (!(spi->mode & SPI_LSB_FIRST))
|
|
cs->hw_mode |= CSMODE_REV;
|
|
|
|
/* Handle the loop mode */
|
|
loop_mode = fsl_espi_read_reg(espi, ESPI_SPMODE);
|
|
loop_mode &= ~SPMODE_LOOP;
|
|
if (spi->mode & SPI_LOOP)
|
|
loop_mode |= SPMODE_LOOP;
|
|
fsl_espi_write_reg(espi, ESPI_SPMODE, loop_mode);
|
|
|
|
fsl_espi_setup_transfer(spi, NULL);
|
|
|
|
pm_runtime_mark_last_busy(espi->dev);
|
|
pm_runtime_put_autosuspend(espi->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fsl_espi_cleanup(struct spi_device *spi)
|
|
{
|
|
struct fsl_espi_cs *cs = spi_get_ctldata(spi);
|
|
|
|
kfree(cs);
|
|
spi_set_ctldata(spi, NULL);
|
|
}
|
|
|
|
static void fsl_espi_cpu_irq(struct fsl_espi *espi, u32 events)
|
|
{
|
|
if (!espi->rx_done)
|
|
fsl_espi_read_rx_fifo(espi, events);
|
|
|
|
if (!espi->tx_done)
|
|
fsl_espi_fill_tx_fifo(espi, events);
|
|
|
|
if (!espi->tx_done || !espi->rx_done)
|
|
return;
|
|
|
|
/* we're done, but check for errors before returning */
|
|
events = fsl_espi_read_reg(espi, ESPI_SPIE);
|
|
|
|
if (!(events & SPIE_DON))
|
|
dev_err(espi->dev,
|
|
"Transfer done but SPIE_DON isn't set!\n");
|
|
|
|
if (SPIE_RXCNT(events) || SPIE_TXCNT(events) != FSL_ESPI_FIFO_SIZE) {
|
|
dev_err(espi->dev, "Transfer done but rx/tx fifo's aren't empty!\n");
|
|
dev_err(espi->dev, "SPIE_RXCNT = %d, SPIE_TXCNT = %d\n",
|
|
SPIE_RXCNT(events), SPIE_TXCNT(events));
|
|
}
|
|
|
|
complete(&espi->done);
|
|
}
|
|
|
|
static irqreturn_t fsl_espi_irq(s32 irq, void *context_data)
|
|
{
|
|
struct fsl_espi *espi = context_data;
|
|
u32 events;
|
|
|
|
spin_lock(&espi->lock);
|
|
|
|
/* Get interrupt events(tx/rx) */
|
|
events = fsl_espi_read_reg(espi, ESPI_SPIE);
|
|
if (!events) {
|
|
spin_unlock(&espi->lock);
|
|
return IRQ_NONE;
|
|
}
|
|
|
|
dev_vdbg(espi->dev, "%s: events %x\n", __func__, events);
|
|
|
|
fsl_espi_cpu_irq(espi, events);
|
|
|
|
/* Clear the events */
|
|
fsl_espi_write_reg(espi, ESPI_SPIE, events);
|
|
|
|
spin_unlock(&espi->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int fsl_espi_runtime_suspend(struct device *dev)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(dev);
|
|
struct fsl_espi *espi = spi_master_get_devdata(master);
|
|
u32 regval;
|
|
|
|
regval = fsl_espi_read_reg(espi, ESPI_SPMODE);
|
|
regval &= ~SPMODE_ENABLE;
|
|
fsl_espi_write_reg(espi, ESPI_SPMODE, regval);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fsl_espi_runtime_resume(struct device *dev)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(dev);
|
|
struct fsl_espi *espi = spi_master_get_devdata(master);
|
|
u32 regval;
|
|
|
|
regval = fsl_espi_read_reg(espi, ESPI_SPMODE);
|
|
regval |= SPMODE_ENABLE;
|
|
fsl_espi_write_reg(espi, ESPI_SPMODE, regval);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static size_t fsl_espi_max_message_size(struct spi_device *spi)
|
|
{
|
|
return SPCOM_TRANLEN_MAX;
|
|
}
|
|
|
|
static void fsl_espi_init_regs(struct device *dev, bool initial)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(dev);
|
|
struct fsl_espi *espi = spi_master_get_devdata(master);
|
|
struct device_node *nc;
|
|
u32 csmode, cs, prop;
|
|
int ret;
|
|
|
|
/* SPI controller initializations */
|
|
fsl_espi_write_reg(espi, ESPI_SPMODE, 0);
|
|
fsl_espi_write_reg(espi, ESPI_SPIM, 0);
|
|
fsl_espi_write_reg(espi, ESPI_SPCOM, 0);
|
|
fsl_espi_write_reg(espi, ESPI_SPIE, 0xffffffff);
|
|
|
|
/* Init eSPI CS mode register */
|
|
for_each_available_child_of_node(master->dev.of_node, nc) {
|
|
/* get chip select */
|
|
ret = of_property_read_u32(nc, "reg", &cs);
|
|
if (ret || cs >= master->num_chipselect)
|
|
continue;
|
|
|
|
csmode = CSMODE_INIT_VAL;
|
|
|
|
/* check if CSBEF is set in device tree */
|
|
ret = of_property_read_u32(nc, "fsl,csbef", &prop);
|
|
if (!ret) {
|
|
csmode &= ~(CSMODE_BEF(0xf));
|
|
csmode |= CSMODE_BEF(prop);
|
|
}
|
|
|
|
/* check if CSAFT is set in device tree */
|
|
ret = of_property_read_u32(nc, "fsl,csaft", &prop);
|
|
if (!ret) {
|
|
csmode &= ~(CSMODE_AFT(0xf));
|
|
csmode |= CSMODE_AFT(prop);
|
|
}
|
|
|
|
fsl_espi_write_reg(espi, ESPI_SPMODEx(cs), csmode);
|
|
|
|
if (initial)
|
|
dev_info(dev, "cs=%u, init_csmode=0x%x\n", cs, csmode);
|
|
}
|
|
|
|
/* Enable SPI interface */
|
|
fsl_espi_write_reg(espi, ESPI_SPMODE, SPMODE_INIT_VAL | SPMODE_ENABLE);
|
|
}
|
|
|
|
static int fsl_espi_probe(struct device *dev, struct resource *mem,
|
|
unsigned int irq, unsigned int num_cs)
|
|
{
|
|
struct spi_master *master;
|
|
struct fsl_espi *espi;
|
|
int ret;
|
|
|
|
master = spi_alloc_master(dev, sizeof(struct fsl_espi));
|
|
if (!master)
|
|
return -ENOMEM;
|
|
|
|
dev_set_drvdata(dev, master);
|
|
|
|
master->mode_bits = SPI_RX_DUAL | SPI_CPOL | SPI_CPHA | SPI_CS_HIGH |
|
|
SPI_LSB_FIRST | SPI_LOOP;
|
|
master->dev.of_node = dev->of_node;
|
|
master->bits_per_word_mask = SPI_BPW_RANGE_MASK(4, 16);
|
|
master->setup = fsl_espi_setup;
|
|
master->cleanup = fsl_espi_cleanup;
|
|
master->transfer_one_message = fsl_espi_do_one_msg;
|
|
master->auto_runtime_pm = true;
|
|
master->max_message_size = fsl_espi_max_message_size;
|
|
master->num_chipselect = num_cs;
|
|
|
|
espi = spi_master_get_devdata(master);
|
|
spin_lock_init(&espi->lock);
|
|
|
|
espi->dev = dev;
|
|
espi->spibrg = fsl_get_sys_freq();
|
|
if (espi->spibrg == -1) {
|
|
dev_err(dev, "Can't get sys frequency!\n");
|
|
ret = -EINVAL;
|
|
goto err_probe;
|
|
}
|
|
/* determined by clock divider fields DIV16/PM in register SPMODEx */
|
|
master->min_speed_hz = DIV_ROUND_UP(espi->spibrg, 4 * 16 * 16);
|
|
master->max_speed_hz = DIV_ROUND_UP(espi->spibrg, 4);
|
|
|
|
init_completion(&espi->done);
|
|
|
|
espi->reg_base = devm_ioremap_resource(dev, mem);
|
|
if (IS_ERR(espi->reg_base)) {
|
|
ret = PTR_ERR(espi->reg_base);
|
|
goto err_probe;
|
|
}
|
|
|
|
/* Register for SPI Interrupt */
|
|
ret = devm_request_irq(dev, irq, fsl_espi_irq, 0, "fsl_espi", espi);
|
|
if (ret)
|
|
goto err_probe;
|
|
|
|
fsl_espi_init_regs(dev, true);
|
|
|
|
pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_TIMEOUT);
|
|
pm_runtime_use_autosuspend(dev);
|
|
pm_runtime_set_active(dev);
|
|
pm_runtime_enable(dev);
|
|
pm_runtime_get_sync(dev);
|
|
|
|
ret = devm_spi_register_master(dev, master);
|
|
if (ret < 0)
|
|
goto err_pm;
|
|
|
|
dev_info(dev, "at 0x%p (irq = %u)\n", espi->reg_base, irq);
|
|
|
|
pm_runtime_mark_last_busy(dev);
|
|
pm_runtime_put_autosuspend(dev);
|
|
|
|
return 0;
|
|
|
|
err_pm:
|
|
pm_runtime_put_noidle(dev);
|
|
pm_runtime_disable(dev);
|
|
pm_runtime_set_suspended(dev);
|
|
err_probe:
|
|
spi_master_put(master);
|
|
return ret;
|
|
}
|
|
|
|
static int of_fsl_espi_get_chipselects(struct device *dev)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
u32 num_cs;
|
|
int ret;
|
|
|
|
ret = of_property_read_u32(np, "fsl,espi-num-chipselects", &num_cs);
|
|
if (ret) {
|
|
dev_err(dev, "No 'fsl,espi-num-chipselects' property\n");
|
|
return 0;
|
|
}
|
|
|
|
return num_cs;
|
|
}
|
|
|
|
static int of_fsl_espi_probe(struct platform_device *ofdev)
|
|
{
|
|
struct device *dev = &ofdev->dev;
|
|
struct device_node *np = ofdev->dev.of_node;
|
|
struct resource mem;
|
|
unsigned int irq, num_cs;
|
|
int ret;
|
|
|
|
if (of_property_read_bool(np, "mode")) {
|
|
dev_err(dev, "mode property is not supported on ESPI!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
num_cs = of_fsl_espi_get_chipselects(dev);
|
|
if (!num_cs)
|
|
return -EINVAL;
|
|
|
|
ret = of_address_to_resource(np, 0, &mem);
|
|
if (ret)
|
|
return ret;
|
|
|
|
irq = irq_of_parse_and_map(np, 0);
|
|
if (!irq)
|
|
return -EINVAL;
|
|
|
|
return fsl_espi_probe(dev, &mem, irq, num_cs);
|
|
}
|
|
|
|
static int of_fsl_espi_remove(struct platform_device *dev)
|
|
{
|
|
pm_runtime_disable(&dev->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int of_fsl_espi_suspend(struct device *dev)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
ret = spi_master_suspend(master);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return pm_runtime_force_suspend(dev);
|
|
}
|
|
|
|
static int of_fsl_espi_resume(struct device *dev)
|
|
{
|
|
struct spi_master *master = dev_get_drvdata(dev);
|
|
int ret;
|
|
|
|
fsl_espi_init_regs(dev, false);
|
|
|
|
ret = pm_runtime_force_resume(dev);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
return spi_master_resume(master);
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static const struct dev_pm_ops espi_pm = {
|
|
SET_RUNTIME_PM_OPS(fsl_espi_runtime_suspend,
|
|
fsl_espi_runtime_resume, NULL)
|
|
SET_SYSTEM_SLEEP_PM_OPS(of_fsl_espi_suspend, of_fsl_espi_resume)
|
|
};
|
|
|
|
static const struct of_device_id of_fsl_espi_match[] = {
|
|
{ .compatible = "fsl,mpc8536-espi" },
|
|
{}
|
|
};
|
|
MODULE_DEVICE_TABLE(of, of_fsl_espi_match);
|
|
|
|
static struct platform_driver fsl_espi_driver = {
|
|
.driver = {
|
|
.name = "fsl_espi",
|
|
.of_match_table = of_fsl_espi_match,
|
|
.pm = &espi_pm,
|
|
},
|
|
.probe = of_fsl_espi_probe,
|
|
.remove = of_fsl_espi_remove,
|
|
};
|
|
module_platform_driver(fsl_espi_driver);
|
|
|
|
MODULE_AUTHOR("Mingkai Hu");
|
|
MODULE_DESCRIPTION("Enhanced Freescale SPI Driver");
|
|
MODULE_LICENSE("GPL");
|