9
0
Fork 0
barebox/drivers/spi/mvebu_spi.c

380 lines
9.0 KiB
C

/*
* Marvell MVEBU SoC SPI controller
* compatible with Dove, Kirkwood, MV78x00, Armada 370/XP
*
* Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com>
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <common.h>
#include <driver.h>
#include <errno.h>
#include <init.h>
#include <io.h>
#include <malloc.h>
#include <spi/spi.h>
#include <linux/clk.h>
#include <linux/err.h>
#define SPI_IF_CTRL 0x00
#define IF_CS_NUM(x) ((x) << 2)
#define IF_CS_NUM_MASK IF_CS_NUM(7)
#define IF_READ_READY BIT(1)
#define IF_CS_ENABLE BIT(0)
#define SPI_IF_CONFIG 0x04
#define IF_CLK_DIV(x) ((x) << 11)
#define IF_CLK_DIV_MASK (0x7 << 11)
#define IF_FAST_READ BIT(10)
#define IF_ADDRESS_LEN_4BYTE (3 << 8)
#define IF_ADDRESS_LEN_3BYTE (2 << 8)
#define IF_ADDRESS_LEN_2BYTE (1 << 8)
#define IF_ADDRESS_LEN_1BYTE (0 << 8)
#define IF_CLK_PRESCALE_POW8 BIT(7)
#define IF_CLK_PRESCALE_POW4 BIT(6)
#define IF_TRANSFER_2BYTE BIT(5)
#define IF_CLK_PRESCALE_POW2 BIT(4)
#define IF_CLK_PRESCALE(x) ((x) & 0x0f)
#define IF_CLK_PRE_PRESCALE(x) (((((x) & 0xc) << 1) | ((x) & 0x1)) << 4)
#define IF_CLK_PRESCALE_MASK (IF_CLK_PRESCALE(7) | IF_CLK_PRE_PRESCALE(7))
#define SPI_DATA_OUT 0x08
#define SPI_DATA_IN 0x0c
#define SPI_INT_CAUSE 0x10
#define SPI_INT_MASK 0x14
#define INT_READ_READY BIT(0)
#define SPI_SPI_MAX_CS 8
struct mvebu_spi {
struct spi_master master;
void __iomem *base;
struct clk *clk;
bool data16;
int (*set_baudrate)(struct mvebu_spi *p, u32 speed);
};
#define priv_from_spi_device(s) \
container_of(s->master, struct mvebu_spi, master);
static inline int mvebu_spi_set_cs(struct mvebu_spi *p, u8 cs, u8 mode, bool en)
{
u32 val;
/*
* Only Armada 370/XP support up to 8 CS signals, for the
* others this register bits are read-only
*/
if (cs > SPI_SPI_MAX_CS)
return -EINVAL;
if (mode & SPI_CS_HIGH)
en = !en;
val = IF_CS_NUM(cs);
if (en)
val |= IF_CS_ENABLE;
writel(val, p->base + SPI_IF_CTRL);
return 0;
}
static int mvebu_spi_set_transfer_size(struct mvebu_spi *p, int size)
{
u32 val;
if (size != 8 && size != 16)
return -EINVAL;
p->data16 = (size == 16);
val = readl(p->base + SPI_IF_CONFIG) & ~IF_TRANSFER_2BYTE;
if (p->data16)
val |= IF_TRANSFER_2BYTE;
writel(val, p->base + SPI_IF_CONFIG);
return 0;
}
static int mvebu_spi_set_baudrate(struct mvebu_spi *p, u32 speed)
{
u32 pscl, val;
/* standard prescaler values: 1,2,4,6,...,30 */
pscl = DIV_ROUND_UP(clk_get_rate(p->clk), speed);
pscl = roundup(pscl, 2);
dev_dbg(p->master.dev, "%s: clk = %lu, speed = %u, pscl = %d\n",
__func__, clk_get_rate(p->clk), speed, pscl);
if (pscl > 30)
return -EINVAL;
val = readl(p->base + SPI_IF_CONFIG) & ~(IF_CLK_PRESCALE_MASK);
val |= IF_CLK_PRESCALE_POW2 | IF_CLK_PRESCALE(pscl/2);
writel(val, p->base + SPI_IF_CONFIG);
return 0;
}
#if defined(CONFIG_ARCH_ARMADA_370) || defined(CONFIG_ARCH_ARMADA_XP)
static int armada_370_xp_spi_set_baudrate(struct mvebu_spi *p, u32 speed)
{
u32 pscl, pdiv, rate, val;
/* prescaler values: 1,2,3,...,15 */
pscl = DIV_ROUND_UP(clk_get_rate(p->clk), speed);
/* additional prescaler divider: 1, 2, 4, 8, 16, 32, 64, 128 */
pdiv = 0; rate = pscl;
while (rate > 15 && pdiv <= 7) {
rate /= 2;
pdiv++;
}
dev_dbg(p->master.dev, "%s: clk = %lu, speed = %u, pscl = %d, pdiv = %d\n",
__func__, clk_get_rate(p->clk), speed, pscl, pdiv);
if (rate > 15 || pdiv > 7)
return -EINVAL;
val = readl(p->base + SPI_IF_CONFIG) & ~(IF_CLK_PRESCALE_MASK);
val |= IF_CLK_PRE_PRESCALE(pdiv) | IF_CLK_PRESCALE(pscl);
writel(val, p->base + SPI_IF_CONFIG);
return 0;
}
#endif
#if defined(CONFIG_ARCH_DOVE)
static int dove_spi_set_baudrate(struct mvebu_spi *p, u32 speed)
{
u32 pscl, sdiv, rate, val;
/* prescaler values: 1,2,3,...,15 and 1,2,4,6,...,30 */
pscl = DIV_ROUND_UP(clk_get_rate(p->clk), speed);
if (pscl > 15)
pscl = roundup(pscl, 2);
/* additional sclk divider: 1, 2, 4, 8, 16 */
sdiv = 0; rate = pscl;
while (rate > 30 && sdiv <= 4) {
rate /= 2;
sdiv++;
}
dev_dbg(p->master.dev, "%s: clk = %lu, speed = %u, pscl = %d, sdiv = %d\n",
__func__, clk_get_rate(p->clk), speed, pscl, sdiv);
if (rate > 30 || sdiv > 4)
return -EINVAL;
val = readl(p->base + SPI_IF_CONFIG) &
~(IF_CLK_DIV_MASK | IF_CLK_PRESCALE_MASK);
val |= IF_CLK_DIV(sdiv);
if (pscl > 15)
val |= IF_CLK_PRESCALE_POW2 | IF_CLK_PRESCALE(pscl/2);
else
val |= IF_CLK_PRESCALE(pscl);
writel(val, p->base + SPI_IF_CONFIG);
return 0;
}
#endif
static int mvebu_spi_set_mode(struct mvebu_spi *p, u8 mode)
{
/*
* From public datasheets of Orion SoCs, it is unclear
* if the SPI controller supports setting CPOL/CPHA.
* Dove has an SCK_INV but as with the other SoCs, it
* is tagged with "Must be 1".
*
* For now, we just bail out if device requests any
* other mode than SPI_MODE0.
*/
if ((mode & (SPI_CPOL|SPI_CPHA)) == SPI_MODE_0)
return 0;
pr_err("%s: unsupported SPI mode %02x\n", __func__, mode);
return -EINVAL;
}
static int mvebu_spi_setup(struct spi_device *spi)
{
int ret;
struct mvebu_spi *priv = priv_from_spi_device(spi);
dev_dbg(&spi->dev, "%s: mode %02x, bits_per_word = %d, speed = %d\n",
__func__, spi->mode, spi->bits_per_word, spi->max_speed_hz);
ret = mvebu_spi_set_cs(priv, spi->chip_select, spi->mode, false);
if (ret)
return ret;
ret = mvebu_spi_set_mode(priv, spi->mode);
if (ret)
return ret;
ret = mvebu_spi_set_transfer_size(priv, spi->bits_per_word);
if (ret)
return ret;
return priv->set_baudrate(priv, spi->max_speed_hz);
}
static inline int mvebu_spi_wait_for_read_ready(struct mvebu_spi *p)
{
int timeout = 100;
while ((readl(p->base + SPI_IF_CTRL) & IF_READ_READY) == 0 &&
timeout--)
udelay(1);
if (timeout < 0)
return -EIO;
return 0;
}
static int mvebu_spi_do_transfer(struct spi_device *spi,
struct spi_transfer *t)
{
const u8 *txdata = t->tx_buf;
u8 *rxdata = t->rx_buf;
int ret = 0, n, inc;
struct mvebu_spi *priv = priv_from_spi_device(spi);
if (t->bits_per_word)
ret = mvebu_spi_set_transfer_size(priv, spi->bits_per_word);
if (ret)
return ret;
if (t->speed_hz)
ret = priv->set_baudrate(priv, t->speed_hz);
if (ret)
return ret;
inc = (priv->data16) ? 2 : 1;
for (n = 0; n < t->len; n += inc) {
u32 data = 0;
if (txdata)
data = *txdata++;
if (txdata && priv->data16)
data |= (*txdata++ << 8);
writel(data, priv->base + SPI_DATA_OUT);
ret = mvebu_spi_wait_for_read_ready(priv);
if (ret) {
dev_err(&spi->dev, "timeout reading from device %s\n",
dev_name(&spi->dev));
return ret;
}
data = readl(priv->base + SPI_DATA_IN);
if (rxdata)
*rxdata++ = (data & 0xff);
if (rxdata && priv->data16)
*rxdata++ = (data >> 8) & 0xff;
}
return 0;
}
static int mvebu_spi_transfer(struct spi_device *spi, struct spi_message *msg)
{
struct spi_transfer *t;
int ret;
struct mvebu_spi *priv = priv_from_spi_device(spi);
ret = mvebu_spi_set_cs(priv, spi->chip_select, spi->mode, true);
if (ret)
return ret;
msg->actual_length = 0;
list_for_each_entry(t, &msg->transfers, transfer_list) {
ret = mvebu_spi_do_transfer(spi, t);
if (ret)
break;
msg->actual_length += t->len;
}
ret = mvebu_spi_set_cs(priv, spi->chip_select, spi->mode, false);
if (ret)
return ret;
return ret;
}
static struct of_device_id mvebu_spi_dt_ids[] = {
{ .compatible = "marvell,orion-spi",
.data = (unsigned long)&mvebu_spi_set_baudrate },
#if defined(CONFIG_ARCH_ARMADA_370) || defined(CONFIG_ARCH_ARMADA_XP)
{ .compatible = "marvell,armada-370-xp-spi",
.data = (unsigned long)&armada_370_xp_spi_set_baudrate },
#endif
#if defined(CONFIG_ARCH_DOVE)
{ .compatible = "marvell,dove-spi",
.data = (unsigned long)&dove_spi_set_baudrate },
#endif
{ }
};
static int mvebu_spi_probe(struct device_d *dev)
{
struct spi_master *master;
struct mvebu_spi *priv;
const struct of_device_id *match;
int ret = 0;
match = of_match_node(mvebu_spi_dt_ids, dev->device_node);
if (!match)
return -EINVAL;
priv = xzalloc(sizeof(*priv));
priv->base = dev_request_mem_region(dev, 0);
if (!priv->base) {
ret = -EINVAL;
goto err_free;
}
priv->set_baudrate = (void *)match->data;
priv->clk = clk_get(dev, NULL);
if (IS_ERR(priv->clk)) {
ret = PTR_ERR(priv->clk);
goto err_free;
}
master = &priv->master;
master->dev = dev;
master->bus_num = dev->id;
master->setup = mvebu_spi_setup;
master->transfer = mvebu_spi_transfer;
master->num_chipselect = 1;
ret = spi_register_master(master);
if (!ret)
return 0;
err_free:
free(priv);
return ret;
}
static struct driver_d mvebu_spi_driver = {
.name = "mvebu-spi",
.probe = mvebu_spi_probe,
.of_compatible = DRV_OF_COMPAT(mvebu_spi_dt_ids),
};
device_platform_driver(mvebu_spi_driver);