uboot/drivers/spi/mvebu_a3700_spi.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * Copyright (C) 2015 Marvell International Ltd.
   4 *
   5 * Copyright (C) 2016 Stefan Roese <sr@denx.de>
   6 */
   7
   8#include <common.h>
   9#include <dm.h>
  10#include <malloc.h>
  11#include <spi.h>
  12#include <wait_bit.h>
  13#include <asm/io.h>
  14
  15DECLARE_GLOBAL_DATA_PTR;
  16
  17#define MVEBU_SPI_A3700_XFER_RDY                BIT(1)
  18#define MVEBU_SPI_A3700_FIFO_FLUSH              BIT(9)
  19#define MVEBU_SPI_A3700_BYTE_LEN                BIT(5)
  20#define MVEBU_SPI_A3700_CLK_PHA                 BIT(6)
  21#define MVEBU_SPI_A3700_CLK_POL                 BIT(7)
  22#define MVEBU_SPI_A3700_FIFO_EN                 BIT(17)
  23#define MVEBU_SPI_A3700_SPI_EN_0                BIT(16)
  24#define MVEBU_SPI_A3700_CLK_PRESCALE_BIT        0
  25#define MVEBU_SPI_A3700_CLK_PRESCALE_MASK       \
  26        (0x1f << MVEBU_SPI_A3700_CLK_PRESCALE_BIT)
  27
  28/* SPI registers */
  29struct spi_reg {
  30        u32 ctrl;       /* 0x10600 */
  31        u32 cfg;        /* 0x10604 */
  32        u32 dout;       /* 0x10608 */
  33        u32 din;        /* 0x1060c */
  34};
  35
  36struct mvebu_spi_platdata {
  37        struct spi_reg *spireg;
  38        unsigned int frequency;
  39        unsigned int clock;
  40};
  41
  42static void spi_cs_activate(struct spi_reg *reg, int cs)
  43{
  44        setbits_le32(&reg->ctrl, MVEBU_SPI_A3700_SPI_EN_0 << cs);
  45}
  46
  47static void spi_cs_deactivate(struct spi_reg *reg, int cs)
  48{
  49        clrbits_le32(&reg->ctrl, MVEBU_SPI_A3700_SPI_EN_0 << cs);
  50}
  51
  52/**
  53 * spi_legacy_shift_byte() - triggers the real SPI transfer
  54 * @bytelen:    Indicate how many bytes to transfer.
  55 * @dout:       Buffer address of what to send.
  56 * @din:        Buffer address of where to receive.
  57 *
  58 * This function triggers the real SPI transfer in legacy mode. It
  59 * will shift out char buffer from @dout, and shift in char buffer to
  60 * @din, if necessary.
  61 *
  62 * This function assumes that only one byte is shifted at one time.
  63 * However, it is not its responisbility to set the transfer type to
  64 * one-byte. Also, it does not guarantee that it will work if transfer
  65 * type becomes two-byte. See spi_set_legacy() for details.
  66 *
  67 * In legacy mode, simply write to the SPI_DOUT register will trigger
  68 * the transfer.
  69 *
  70 * If @dout == NULL, which means no actual data needs to be sent out,
  71 * then the function will shift out 0x00 in order to shift in data.
  72 * The XFER_RDY flag is checked every time before accessing SPI_DOUT
  73 * and SPI_DIN register.
  74 *
  75 * The number of transfers to be triggerred is decided by @bytelen.
  76 *
  77 * Return:      0 - cool
  78 *              -ETIMEDOUT - XFER_RDY flag timeout
  79 */
  80static int spi_legacy_shift_byte(struct spi_reg *reg, unsigned int bytelen,
  81                                 const void *dout, void *din)
  82{
  83        const u8 *dout_8;
  84        u8 *din_8;
  85        int ret;
  86
  87        /* Use 0x00 as dummy dout */
  88        const u8 dummy_dout = 0x0;
  89        u32 pending_dout = 0x0;
  90
  91        /* dout_8: pointer of current dout */
  92        dout_8 = dout;
  93        /* din_8: pointer of current din */
  94        din_8 = din;
  95
  96        while (bytelen) {
  97                ret = wait_for_bit_le32(&reg->ctrl,
  98                                        MVEBU_SPI_A3700_XFER_RDY,
  99                                        true,100, false);
 100                if (ret)
 101                        return ret;
 102
 103                if (dout)
 104                        pending_dout = (u32)*dout_8;
 105                else
 106                        pending_dout = (u32)dummy_dout;
 107
 108                /* Trigger the xfer */
 109                writel(pending_dout, &reg->dout);
 110
 111                if (din) {
 112                        ret = wait_for_bit_le32(&reg->ctrl,
 113                                                MVEBU_SPI_A3700_XFER_RDY,
 114                                                true, 100, false);
 115                        if (ret)
 116                                return ret;
 117
 118                        /* Read what is transferred in */
 119                        *din_8 = (u8)readl(&reg->din);
 120                }
 121
 122                /* Don't increment the current pointer if NULL */
 123                if (dout)
 124                        dout_8++;
 125                if (din)
 126                        din_8++;
 127
 128                bytelen--;
 129        }
 130
 131        return 0;
 132}
 133
 134static int mvebu_spi_xfer(struct udevice *dev, unsigned int bitlen,
 135                          const void *dout, void *din, unsigned long flags)
 136{
 137        struct udevice *bus = dev->parent;
 138        struct mvebu_spi_platdata *plat = dev_get_platdata(bus);
 139        struct spi_reg *reg = plat->spireg;
 140        unsigned int bytelen;
 141        int ret;
 142
 143        bytelen = bitlen / 8;
 144
 145        if (dout && din)
 146                debug("This is a duplex transfer.\n");
 147
 148        /* Activate CS */
 149        if (flags & SPI_XFER_BEGIN) {
 150                debug("SPI: activate cs.\n");
 151                spi_cs_activate(reg, spi_chip_select(dev));
 152        }
 153
 154        /* Send and/or receive */
 155        if (dout || din) {
 156                ret = spi_legacy_shift_byte(reg, bytelen, dout, din);
 157                if (ret)
 158                        return ret;
 159        }
 160
 161        /* Deactivate CS */
 162        if (flags & SPI_XFER_END) {
 163                ret = wait_for_bit_le32(&reg->ctrl,
 164                                        MVEBU_SPI_A3700_XFER_RDY,
 165                                        true, 100, false);
 166                if (ret)
 167                        return ret;
 168
 169                debug("SPI: deactivate cs.\n");
 170                spi_cs_deactivate(reg, spi_chip_select(dev));
 171        }
 172
 173        return 0;
 174}
 175
 176static int mvebu_spi_set_speed(struct udevice *bus, uint hz)
 177{
 178        struct mvebu_spi_platdata *plat = dev_get_platdata(bus);
 179        struct spi_reg *reg = plat->spireg;
 180        u32 data;
 181
 182        data = readl(&reg->cfg);
 183
 184        /* Set Prescaler */
 185        data &= ~MVEBU_SPI_A3700_CLK_PRESCALE_MASK;
 186
 187        /* Calculate Prescaler = (spi_input_freq / spi_max_freq) */
 188        if (hz > plat->frequency)
 189                hz = plat->frequency;
 190        data |= plat->clock / hz;
 191
 192        writel(data, &reg->cfg);
 193
 194        return 0;
 195}
 196
 197static int mvebu_spi_set_mode(struct udevice *bus, uint mode)
 198{
 199        struct mvebu_spi_platdata *plat = dev_get_platdata(bus);
 200        struct spi_reg *reg = plat->spireg;
 201
 202        /*
 203         * Set SPI polarity
 204         * 0: Serial interface clock is low when inactive
 205         * 1: Serial interface clock is high when inactive
 206         */
 207        if (mode & SPI_CPOL)
 208                setbits_le32(&reg->cfg, MVEBU_SPI_A3700_CLK_POL);
 209        else
 210                clrbits_le32(&reg->cfg, MVEBU_SPI_A3700_CLK_POL);
 211        if (mode & SPI_CPHA)
 212                setbits_le32(&reg->cfg, MVEBU_SPI_A3700_CLK_PHA);
 213        else
 214                clrbits_le32(&reg->cfg, MVEBU_SPI_A3700_CLK_PHA);
 215
 216        return 0;
 217}
 218
 219static int mvebu_spi_probe(struct udevice *bus)
 220{
 221        struct mvebu_spi_platdata *plat = dev_get_platdata(bus);
 222        struct spi_reg *reg = plat->spireg;
 223        u32 data;
 224        int ret;
 225
 226        /*
 227         * Settings SPI controller to be working in legacy mode, which
 228         * means use only DO pin (I/O 1) for Data Out, and DI pin (I/O 0)
 229         * for Data In.
 230         */
 231
 232        /* Flush read/write FIFO */
 233        data = readl(&reg->cfg);
 234        writel(data | MVEBU_SPI_A3700_FIFO_FLUSH, &reg->cfg);
 235        ret = wait_for_bit_le32(&reg->cfg, MVEBU_SPI_A3700_FIFO_FLUSH,
 236                                false, 1000, false);
 237        if (ret)
 238                return ret;
 239
 240        /* Disable FIFO mode */
 241        data &= ~MVEBU_SPI_A3700_FIFO_EN;
 242
 243        /* Always shift 1 byte at a time */
 244        data &= ~MVEBU_SPI_A3700_BYTE_LEN;
 245
 246        writel(data, &reg->cfg);
 247
 248        return 0;
 249}
 250
 251static int mvebu_spi_ofdata_to_platdata(struct udevice *bus)
 252{
 253        struct mvebu_spi_platdata *plat = dev_get_platdata(bus);
 254
 255        plat->spireg = (struct spi_reg *)devfdt_get_addr(bus);
 256
 257        /*
 258         * FIXME
 259         * Right now, mvebu does not have a clock infrastructure in U-Boot
 260         * which should be used to query the input clock to the SPI
 261         * controller. Once this clock driver is integrated into U-Boot
 262         * it should be used to read the input clock and the DT property
 263         * can be removed.
 264         */
 265        plat->clock = fdtdec_get_int(gd->fdt_blob, dev_of_offset(bus),
 266                                     "clock-frequency", 160000);
 267        plat->frequency = fdtdec_get_int(gd->fdt_blob, dev_of_offset(bus),
 268                                         "spi-max-frequency", 40000);
 269
 270        return 0;
 271}
 272
 273static const struct dm_spi_ops mvebu_spi_ops = {
 274        .xfer           = mvebu_spi_xfer,
 275        .set_speed      = mvebu_spi_set_speed,
 276        .set_mode       = mvebu_spi_set_mode,
 277        /*
 278         * cs_info is not needed, since we require all chip selects to be
 279         * in the device tree explicitly
 280         */
 281};
 282
 283static const struct udevice_id mvebu_spi_ids[] = {
 284        { .compatible = "marvell,armada-3700-spi" },
 285        { }
 286};
 287
 288U_BOOT_DRIVER(mvebu_spi) = {
 289        .name = "mvebu_spi",
 290        .id = UCLASS_SPI,
 291        .of_match = mvebu_spi_ids,
 292        .ops = &mvebu_spi_ops,
 293        .ofdata_to_platdata = mvebu_spi_ofdata_to_platdata,
 294        .platdata_auto_alloc_size = sizeof(struct mvebu_spi_platdata),
 295        .probe = mvebu_spi_probe,
 296};
 297