#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <linux/spi/spi.h>
#include <asm/mpc52xx_psc.h>
enum {
TYPE_MPC5121,
TYPE_MPC5125,
};
#define psc_addr(mps, regname) ({ \
void *__ret = NULL; \
switch (mps->type) { \
case TYPE_MPC5121: { \
struct mpc52xx_psc __iomem *psc = mps->psc; \
__ret = &psc->regname; \
}; \
break; \
case TYPE_MPC5125: { \
struct mpc5125_psc __iomem *psc = mps->psc; \
__ret = &psc->regname; \
}; \
break; \
} \
__ret; })
struct mpc512x_psc_spi {
int type;
void __iomem *psc;
struct mpc512x_psc_fifo __iomem *fifo;
int irq;
u8 bits_per_word;
u32 mclk_rate;
struct completion txisrdone;
};
struct mpc512x_psc_spi_cs {
int bits_per_word;
int speed_hz;
};
static int mpc512x_psc_spi_transfer_setup(struct spi_device *spi,
struct spi_transfer *t)
{
struct mpc512x_psc_spi_cs *cs = spi->controller_state;
cs->speed_hz = (t && t->speed_hz)
? t->speed_hz : spi->max_speed_hz;
cs->bits_per_word = (t && t->bits_per_word)
? t->bits_per_word : spi->bits_per_word;
cs->bits_per_word = ((cs->bits_per_word + 7) / 8) * 8;
return 0;
}
static void mpc512x_psc_spi_activate_cs(struct spi_device *spi)
{
struct mpc512x_psc_spi_cs *cs = spi->controller_state;
struct mpc512x_psc_spi *mps = spi_controller_get_devdata(spi->controller);
u32 sicr;
u32 ccr;
int speed;
u16 bclkdiv;
sicr = in_be32(psc_addr(mps, sicr));
if (spi->mode & SPI_CPHA)
sicr |= 0x00001000;
else
sicr &= ~0x00001000;
if (spi->mode & SPI_CPOL)
sicr |= 0x00002000;
else
sicr &= ~0x00002000;
if (spi->mode & SPI_LSB_FIRST)
sicr |= 0x10000000;
else
sicr &= ~0x10000000;
out_be32(psc_addr(mps, sicr), sicr);
ccr = in_be32(psc_addr(mps, ccr));
ccr &= 0xFF000000;
speed = cs->speed_hz;
if (!speed)
speed = 1000000;
bclkdiv = (mps->mclk_rate / speed) - 1;
ccr |= (((bclkdiv & 0xff) << 16) | (((bclkdiv >> 8) & 0xff) << 8));
out_be32(psc_addr(mps, ccr), ccr);
mps->bits_per_word = cs->bits_per_word;
if (spi_get_csgpiod(spi, 0)) {
gpiod_set_value(spi_get_csgpiod(spi, 0), 1);
}
}
static void mpc512x_psc_spi_deactivate_cs(struct spi_device *spi)
{
if (spi_get_csgpiod(spi, 0)) {
gpiod_set_value(spi_get_csgpiod(spi, 0), 0);
}
}
#define MPC512x_PSC_FIFO_SZ(sz) ((sz & 0x7ff) << 2);
#define EOFBYTE 1
static int mpc512x_psc_spi_transfer_rxtx(struct spi_device *spi,
struct spi_transfer *t)
{
struct mpc512x_psc_spi *mps = spi_controller_get_devdata(spi->controller);
struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
size_t tx_len = t->len;
size_t rx_len = t->len;
u8 *tx_buf = (u8 *)t->tx_buf;
u8 *rx_buf = (u8 *)t->rx_buf;
if (!tx_buf && !rx_buf && t->len)
return -EINVAL;
while (rx_len || tx_len) {
size_t txcount;
u8 data;
size_t fifosz;
size_t rxcount;
int rxtries;
fifosz = MPC512x_PSC_FIFO_SZ(in_be32(&fifo->txsz));
txcount = min(fifosz, tx_len);
fifosz = MPC512x_PSC_FIFO_SZ(in_be32(&fifo->rxsz));
fifosz -= in_be32(&fifo->rxcnt) + 1;
txcount = min(fifosz, txcount);
if (txcount) {
while (txcount-- > 0) {
data = tx_buf ? *tx_buf++ : 0;
if (tx_len == EOFBYTE && t->cs_change)
setbits32(&fifo->txcmd,
MPC512x_PSC_FIFO_EOF);
out_8(&fifo->txdata_8, data);
tx_len--;
}
reinit_completion(&mps->txisrdone);
out_be32(&fifo->txisr, MPC512x_PSC_FIFO_EMPTY);
out_be32(&fifo->tximr, MPC512x_PSC_FIFO_EMPTY);
wait_for_completion(&mps->txisrdone);
}
rxtries = 50;
do {
fifosz = in_be32(&fifo->rxcnt);
rxcount = min(fifosz, rx_len);
while (rxcount-- > 0) {
data = in_8(&fifo->rxdata_8);
if (rx_buf)
*rx_buf++ = data;
rx_len--;
}
if (tx_len)
break;
if (!rx_len)
break;
usleep_range(10, 100);
} while (--rxtries > 0);
if (!tx_len && rx_len && !rxtries) {
rxcount = in_be32(&fifo->rxcnt);
dev_warn(&spi->dev,
"short xfer, missing %zd RX bytes, FIFO level %zd\n",
rx_len, rxcount);
}
if (!tx_len && !rx_len) {
while (in_be32(&fifo->rxcnt))
in_8(&fifo->rxdata_8);
}
}
return 0;
}
static int mpc512x_psc_spi_msg_xfer(struct spi_controller *host,
struct spi_message *m)
{
struct spi_device *spi;
unsigned cs_change;
int status;
struct spi_transfer *t;
spi = m->spi;
cs_change = 1;
status = 0;
list_for_each_entry(t, &m->transfers, transfer_list) {
status = mpc512x_psc_spi_transfer_setup(spi, t);
if (status < 0)
break;
if (cs_change)
mpc512x_psc_spi_activate_cs(spi);
cs_change = t->cs_change;
status = mpc512x_psc_spi_transfer_rxtx(spi, t);
if (status)
break;
m->actual_length += t->len;
spi_transfer_delay_exec(t);
if (cs_change)
mpc512x_psc_spi_deactivate_cs(spi);
}
m->status = status;
if (m->complete)
m->complete(m->context);
if (status || !cs_change)
mpc512x_psc_spi_deactivate_cs(spi);
mpc512x_psc_spi_transfer_setup(spi, NULL);
spi_finalize_current_message(host);
return status;
}
static int mpc512x_psc_spi_prep_xfer_hw(struct spi_controller *host)
{
struct mpc512x_psc_spi *mps = spi_controller_get_devdata(host);
dev_dbg(&host->dev, "%s()\n", __func__);
in_8(psc_addr(mps, mr2));
out_8(psc_addr(mps, mr2), 0x0);
out_8(psc_addr(mps, command), MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE);
return 0;
}
static int mpc512x_psc_spi_unprep_xfer_hw(struct spi_controller *host)
{
struct mpc512x_psc_spi *mps = spi_controller_get_devdata(host);
struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
dev_dbg(&host->dev, "%s()\n", __func__);
out_8(psc_addr(mps, command), MPC52xx_PSC_TX_DISABLE | MPC52xx_PSC_RX_DISABLE);
out_be32(&fifo->tximr, 0);
return 0;
}
static int mpc512x_psc_spi_setup(struct spi_device *spi)
{
struct mpc512x_psc_spi_cs *cs = spi->controller_state;
if (spi->bits_per_word % 8)
return -EINVAL;
if (!cs) {
cs = kzalloc_obj(*cs);
if (!cs)
return -ENOMEM;
spi->controller_state = cs;
}
cs->bits_per_word = spi->bits_per_word;
cs->speed_hz = spi->max_speed_hz;
return 0;
}
static void mpc512x_psc_spi_cleanup(struct spi_device *spi)
{
kfree(spi->controller_state);
}
static int mpc512x_psc_spi_port_config(struct spi_controller *host,
struct mpc512x_psc_spi *mps)
{
struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
u32 sicr;
u32 ccr;
int speed;
u16 bclkdiv;
out_8(psc_addr(mps, command), MPC52xx_PSC_RST_RX);
out_8(psc_addr(mps, command), MPC52xx_PSC_RST_TX);
out_8(psc_addr(mps, command), MPC52xx_PSC_TX_DISABLE | MPC52xx_PSC_RX_DISABLE);
out_be16(psc_addr(mps, isr_imr.imr), 0);
out_be32(&fifo->tximr, 0);
out_be32(&fifo->rximr, 0);
sicr = 0x01000000 |
0x00800000 |
0x00008000 |
0x00004000 |
0x00000800;
out_be32(psc_addr(mps, sicr), sicr);
ccr = in_be32(psc_addr(mps, ccr));
ccr &= 0xFF000000;
speed = 1000000;
bclkdiv = (mps->mclk_rate / speed) - 1;
ccr |= (((bclkdiv & 0xff) << 16) | (((bclkdiv >> 8) & 0xff) << 8));
out_be32(psc_addr(mps, ccr), ccr);
out_8(psc_addr(mps, ctur), 0x00);
out_8(psc_addr(mps, ctlr), 0x82);
out_be32(&fifo->rxalarm, 0xfff);
out_be32(&fifo->txalarm, 0);
out_be32(&fifo->rxcmd,
MPC512x_PSC_FIFO_ENABLE_SLICE | MPC512x_PSC_FIFO_ENABLE_DMA);
out_be32(&fifo->txcmd,
MPC512x_PSC_FIFO_ENABLE_SLICE | MPC512x_PSC_FIFO_ENABLE_DMA);
mps->bits_per_word = 8;
return 0;
}
static irqreturn_t mpc512x_psc_spi_isr(int irq, void *dev_id)
{
struct mpc512x_psc_spi *mps = (struct mpc512x_psc_spi *)dev_id;
struct mpc512x_psc_fifo __iomem *fifo = mps->fifo;
if (in_be32(&fifo->txisr) &
in_be32(&fifo->tximr) & MPC512x_PSC_FIFO_EMPTY) {
out_be32(&fifo->txisr, MPC512x_PSC_FIFO_EMPTY);
out_be32(&fifo->tximr, 0);
complete(&mps->txisrdone);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int mpc512x_psc_spi_of_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mpc512x_psc_spi *mps;
struct spi_controller *host;
int ret;
void *tempp;
struct clk *clk;
host = devm_spi_alloc_host(dev, sizeof(*mps));
if (host == NULL)
return -ENOMEM;
dev_set_drvdata(dev, host);
mps = spi_controller_get_devdata(host);
mps->type = (int)device_get_match_data(dev);
host->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH | SPI_LSB_FIRST;
host->setup = mpc512x_psc_spi_setup;
host->prepare_transfer_hardware = mpc512x_psc_spi_prep_xfer_hw;
host->transfer_one_message = mpc512x_psc_spi_msg_xfer;
host->unprepare_transfer_hardware = mpc512x_psc_spi_unprep_xfer_hw;
host->use_gpio_descriptors = true;
host->cleanup = mpc512x_psc_spi_cleanup;
tempp = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(tempp))
return dev_err_probe(dev, PTR_ERR(tempp), "could not ioremap I/O port range\n");
mps->psc = tempp;
mps->fifo =
(struct mpc512x_psc_fifo *)(tempp + sizeof(struct mpc52xx_psc));
mps->irq = platform_get_irq(pdev, 0);
if (mps->irq < 0)
return mps->irq;
ret = devm_request_irq(dev, mps->irq, mpc512x_psc_spi_isr, IRQF_SHARED,
"mpc512x-psc-spi", mps);
if (ret)
return ret;
init_completion(&mps->txisrdone);
clk = devm_clk_get_enabled(dev, "mclk");
if (IS_ERR(clk))
return PTR_ERR(clk);
mps->mclk_rate = clk_get_rate(clk);
clk = devm_clk_get_enabled(dev, "ipg");
if (IS_ERR(clk))
return PTR_ERR(clk);
ret = mpc512x_psc_spi_port_config(host, mps);
if (ret < 0)
return ret;
return devm_spi_register_controller(dev, host);
}
static const struct of_device_id mpc512x_psc_spi_of_match[] = {
{ .compatible = "fsl,mpc5121-psc-spi", .data = (void *)TYPE_MPC5121 },
{ .compatible = "fsl,mpc5125-psc-spi", .data = (void *)TYPE_MPC5125 },
{},
};
MODULE_DEVICE_TABLE(of, mpc512x_psc_spi_of_match);
static struct platform_driver mpc512x_psc_spi_of_driver = {
.probe = mpc512x_psc_spi_of_probe,
.driver = {
.name = "mpc512x-psc-spi",
.of_match_table = mpc512x_psc_spi_of_match,
},
};
module_platform_driver(mpc512x_psc_spi_of_driver);
MODULE_AUTHOR("John Rigby");
MODULE_DESCRIPTION("MPC512x PSC SPI Driver");
MODULE_LICENSE("GPL");